Native JavaScript sort performing slower than implemented mergesort and quicksort

后端 未结 1 393
醉话见心
醉话见心 2020-12-24 08:12

I\'ve implemented a mergesort and a quicksort to compare them with the native JavaScript sort. For the quicksort I\'ve tried to use this algorithm: view algorithm on youtube

相关标签:
1条回答
  • 2020-12-24 09:12

    So why is the native sort slower? Looking at the code in

    https://github.com/v8/v8/blob/0c76b0ae850027006d5ec0d92449e449d996d3bb/src/js/array.js#L744

    The issue seems to be GetThirdIndex(). It gets called when partition size is > 1000, and I assume it's used to prevent quicksort worst case performance, but the overhead is significant, as it creates internal arrays of pairs and sorts those, and the sorting of those pairs can result in further recursive calls to GetThirdIndex(). This is combined with the recursive calls related to partitioning the original array and to partitioning the internal arrays of pairs.

    Since the test data for these examples is random data, Relu's quicksort doesn't need something like GetThirdIndex(). There's also the check for "holes" in the array, but I assume this isn't significant.

    An alternative to GetThirdIndex() would be an in place median of medians:

    http://en.wikipedia.org/wiki/Median_of_medians

    Merge sort is faster than quicksort with these methods used to prevent worst case scenarios, but it needs an auxiliary array the same size or half the size of the original array.

    Introsort, which is a hybrid of quicksort and heapsort, switching to heapsort if the level of recursion gets too deep would be an alternative:

    http://en.wikipedia.org/wiki/Introsort

    The second merge sort example below uses a compare function, to make a fair comparison. It's significantly faster than the native version. In the case of Chrome, a compare function didn't affect the overall time much. In the case of Firefox, the compare function had more effect. In the case of Firefox, the native version failed for out of memory so I couldn't compare that.

    These are somewhat faster versions of top down merge sort which the original poster was "curious" about, using mutually recursive functions to avoid copy and somewhat optimized merge() (two conditionals per compare).

    Results with Firefox (times vary somewhat)

    native sort - failed for out of memory.
    Relu's merge sort - 1.8 seconds
    Relu's quick sort - 1.3 seconds
    optimized merge sort - 1.4 seconds
    optimized merge sort with compare - 1.8 seconds
    

    Results with Chrome (times vary somewhat)

    native sort - 5.3 seconds
    Relu's merge sort - 2.1 seconds
    Relu's quick sort - 1.8 seconds
    optimized merge sort - 1.6 seconds
    optimized merge sort with compare - 1.7 seconds
    

    Merge Sort

    var length = 10000000; //  ten millions;
    var arr = [];
    for (let i = length; i > 0; i--) {
      // random array
      arr.push(parseInt(Math.random() * 1000000000));
    }
    var mergeSort = function(array) {
      function merge(arr, aux, lo, mid, hi) {
        var i = lo;
        var j = mid + 1;
        var k = lo;
        while(true){
          if(arr[i] <= arr[j]){
            aux[k++] = arr[i++];
            if(i > mid){
              do
                aux[k++] = arr[j++];
              while(j <= hi);
              break;
            }
          } else {
            aux[k++] = arr[j++];
            if(j > hi){
              do
                aux[k++] = arr[i++];
              while(i <= mid);
              break;
            }
          }
        }
      }
    
      function sortarrtoaux(arr, aux, lo, hi) {
        if (hi < lo) return;
        if (hi == lo){
            aux[lo] = arr[lo];
            return;
        }
        var mid = Math.floor(lo + (hi - lo) / 2);
        sortarrtoarr(arr, aux, lo, mid);
        sortarrtoarr(arr, aux, mid + 1, hi);
        merge(arr, aux, lo, mid, hi);
      }
    
      function sortarrtoarr(arr, aux, lo, hi) {
        if (hi <= lo) return;
        var mid = Math.floor(lo + (hi - lo) / 2);
        sortarrtoaux(arr, aux, lo, mid);
        sortarrtoaux(arr, aux, mid + 1, hi);
        merge(aux, arr, lo, mid, hi);
      }
    
      function merge_sort(arr) {
        var aux = arr.slice(0);
        sortarrtoarr(arr, aux, 0, arr.length - 1);
        return arr;
      }
    
      return merge_sort(array);
    }
    
    console.time('measure');
    mergeSort(arr);
    console.timeEnd('measure');
    console.log(arr[0], arr[1]);

    Merge Sort with compare function

    var length = 10000000; //  ten millions;
    var arr = [];
    for (let i = length; i > 0; i--) {
      // random array
      arr.push(parseInt(Math.random() * 1000000000));
    }
    var mergeSort = function(array, comparefn) {
      function merge(arr, aux, lo, mid, hi, comparefn) {
        var i = lo;
        var j = mid + 1;
        var k = lo;
        while(true){
          var cmp = comparefn(arr[i], arr[j]);
          if(cmp <= 0){
            aux[k++] = arr[i++];
            if(i > mid){
              do
                aux[k++] = arr[j++];
              while(j <= hi);
              break;
            }
          } else {
            aux[k++] = arr[j++];
            if(j > hi){
              do
                aux[k++] = arr[i++];
              while(i <= mid);
              break;
            }
          }
        }
      }
    
      function sortarrtoaux(arr, aux, lo, hi, comparefn) {
        if (hi < lo) return;
        if (hi == lo){
            aux[lo] = arr[lo];
            return;
        }
        var mid = Math.floor(lo + (hi - lo) / 2);
        sortarrtoarr(arr, aux, lo, mid, comparefn);
        sortarrtoarr(arr, aux, mid + 1, hi, comparefn);
        merge(arr, aux, lo, mid, hi, comparefn);
      }
    
      function sortarrtoarr(arr, aux, lo, hi, comparefn) {
        if (hi <= lo) return;
        var mid = Math.floor(lo + (hi - lo) / 2);
        sortarrtoaux(arr, aux, lo, mid, comparefn);
        sortarrtoaux(arr, aux, mid + 1, hi, comparefn);
        merge(aux, arr, lo, mid, hi, comparefn);
      }
    
      function merge_sort(arr, comparefn) {
        var aux = arr.slice(0);
        sortarrtoarr(arr, aux, 0, arr.length - 1, comparefn);
        return arr;
      }
    
      return merge_sort(array, comparefn);
    }
    
    console.time('measure');
    mergeSort(arr, function compareNumbers(a, b) {
      return a - b;
    });
    console.timeEnd('measure');
    // check result
    for (let i = 1; i < length; i++) {
        if(arr[i] < arr[i-1]){
            console.log('error');
            break;
        }
    }
    console.log(arr[0], arr[1]);

    Side note: native sort is not stable:

    Native Javascript Sort - test for stability

    var length = 100000;
    var arr = [];
    var j;
    for (let i = 0; i < length; i++) {
      j = parseInt(Math.random() * 100);
      arr[i] = [j, i];
    }
    
    console.time('measure');
    arr.sort(function compareNumbers(a, b) {
      return a[0] - b[0];
    });
    console.timeEnd('measure');
    
    for (let i = 1; i < length; i++) {
        if( (arr[i][0] == arr[i-1][0]) &&
            (arr[i][1] <  arr[i-1][1]) ){
            console.log('not stable');
            console.log(arr[i-1][0], arr[i-1][1]);
            console.log(arr[i  ][0], arr[i  ][1]);
            break;
        }
    }

    Native Javascript Sort - change compare to make it stable

    var length = 100000;
    var arr = [];
    var j;
    for (let i = 0; i < length; i++) {
      j = parseInt(Math.random() * 100);
      arr[i] = [j, i];
    }
    
    console.time('measure');
    arr.sort(function compareNumbers(a, b) {
      if(a[0] == b[0])
        return a[1] - b[1];
      return a[0] - b[0];
    });
    console.timeEnd('measure');
    
    for (let i = 1; i < length; i++) {
        if( (arr[i][0] == arr[i-1][0]) &&
            (arr[i][1] <  arr[i-1][1]) ){
            console.log('not stable');
            console.log(arr[i-1][0], arr[i-1][1]);
            console.log(arr[i  ][0], arr[i  ][1]);
            break;
        }
    }

    0 讨论(0)
提交回复
热议问题