【问题标题】:Fastest way to sort 32bit signed integer arrays in JavaScript?在 JavaScript 中对 32 位有符号整数数组进行排序的最快方法?
【发布时间】:2011-12-26 07:32:55
【问题描述】:
_radixSort_0 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
            0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
/*
RADIX SORT
Use 256 bins
Use shadow array
- Get counts
- Transform counts to pointers
- Sort from LSB - MSB
*/
function radixSort(intArr) {
    var cpy = new Int32Array(intArr.length);
    var c4 = [].concat(_radixSort_0); 
    var c3 = [].concat(_radixSort_0); 
    var c2 = [].concat(_radixSort_0);
    var c1 = [].concat(_radixSort_0); 
    var o4 = 0; var t4;
    var o3 = 0; var t3;
    var o2 = 0; var t2;
    var o1 = 0; var t1;
    var x;
    for(x=0; x<intArr.length; x++) {
        t4 = intArr[x] & 0xFF;
        t3 = (intArr[x] >> 8) & 0xFF;
        t2 = (intArr[x] >> 16) & 0xFF;
        t1 = (intArr[x] >> 24) & 0xFF ^ 0x80;
        c4[t4]++;
        c3[t3]++;
        c2[t2]++;
        c1[t1]++;
    }
    for (x=0; x<256; x++) {
        t4 = o4 + c4[x];
        t3 = o3 + c3[x];
        t2 = o2 + c2[x];
        t1 = o1 + c1[x];
        c4[x] = o4;
        c3[x] = o3;
        c2[x] = o2;
        c1[x] = o1;
        o4 = t4;
        o3 = t3;
        o2 = t2;
        o1 = t1;
    }
    for(x=0; x<intArr.length; x++) {
        t4 = intArr[x] & 0xFF;
        cpy[c4[t4]] = intArr[x];
        c4[t4]++;
    }
    for(x=0; x<intArr.length; x++) {
        t3 = (cpy[x] >> 8) & 0xFF;
        intArr[c3[t3]] = cpy[x];
        c3[t3]++;
    }
    for(x=0; x<intArr.length; x++) {
        t2 = (intArr[x] >> 16) & 0xFF;
        cpy[c2[t2]] = intArr[x];
        c2[t2]++;
    }
    for(x=0; x<intArr.length; x++) {
        t1 = (cpy[x] >> 24) & 0xFF ^ 0x80;
        intArr[c1[t1]] = cpy[x];
        c1[t1]++;
    }
    return intArr;
}

编辑:

到目前为止,最好的/唯一的主要优化是 JS 类型数组。 对普通基数排序的影子数组使用类型化数组已经产生了最好的结果。我还能够使用内置堆栈推送/弹出的 JS 从就地快速排序中挤出一些额外的东西。


latest jsfiddle benchmark

Intel i7 870, 4GB, FireFox 8.0
2mil
radixSort(intArr): 172 ms
radixSortIP(intArr): 1738 ms
quickSortIP(arr): 661 ms
200k
radixSort(intArr): 18 ms
radixSortIP(intArr): 26 ms
quickSortIP(arr): 58 ms

看来标准基数排序确实是这个工作流程的王道。如果有人有时间尝试循环展开或其他修改,我将不胜感激。

我有一个特定的用例,我希望在 JavaScript 中实现最快的排序实现。客户端脚本将访问大型(50,000 - 2 百万)、未排序(基本上是随机)、整数(32 位有符号)数组,然后需要对这些数据进行排序和呈现。

我已经实现了相当快的原地基数排序和原地快速排序jsfiddle benchmark,但对于我的上限数组长度,它们仍然相当慢。快速排序在我的上限数组大小上表现更好,而基数排序在我的下限上表现更好。

defaultSort is the built-in JavaScript array.sort with an integer compare function

Intel C2Q 9650, 4GB, FireFox 3.6
2mil
radixSortIP(intArr): 5554 ms
quickSortIP(arr): 1796 ms
200k
radixSortIP(intArr): 139 ms
quickSortIP(arr): 190 ms
defaultSort(intArr): 354 ms

Intel i7 870, 4GB, FireFox 8.0
2mil
radixSortIP(intArr): 990 ms
quickSortIP(arr): 882 ms
defaultSort(intArr): 3632 ms
200k
radixSortIP(intArr): 28 ms
quickSortIP(arr): 68 ms
defaultSort(intArr): 306 ms

问题

  • 是否有更好的排序算法实现可以满足我的用例/需求?
  • 是否可以对我的就地基数/快速排序实现进行任何优化以提高性能?
    • 有没有一种有效的方法可以将我的就地基数排序从递归函数转换为迭代函数?内存和执行速度。

目标

  • 我希望这些答案能帮助我在基准测试中获得约 20-30% 的性能提升。

澄清/说明

  • “DEFINE FAST”我更喜欢它在所有现代浏览器上运行良好的一般情况,但如果有一个特定于浏览器的优化可以带来显着的改进,这可能是可以接受的。
  • 排序可以在服务器端完成,但我宁愿避免这种情况,因为 JS 应用程序可能会成为独立应用程序(与一些现成的专有应用程序配对,将传感器数据流式传输到文件)。
  • JavaScript 可能不是最好的语言,但它是必需的。
  • 我已经问过这个问题https://stackoverflow.com/questions/7111525/fastest-way-to-sort-integer-arrays-in-javascript 一个不正确的答案被投票,问题被关闭了。
  • 我尝试使用多个浏览器窗口实例作为临时多线程;它没有成功。我会对有关生成多个并发窗口的有用信息感兴趣。

【问题讨论】:

  • 非常有趣的是基数排序比快速排序慢。您确定您正确实施了吗?创建数组花了多少时间?
  • 您是否检查过使用浮点数填充数组时的性能提升? test[x]=parseFloat(..rand..)
  • 在你的分区中使用三的中位数可能会给你的快速排序实现 10% 的加速,并且会避免快速排序在部分有序子集上的大部分问题。更好的是五位数的中位数,这样可以避免常见的三名杀手的中位数。
  • 也许创建一个jsperf.com 页面来反映已尝试的解决方案?
  • 只是我的 2 美分... 200k 个 4 字节的项目可能适合大多数现代处理器的片上 CPU 缓存,而 200 万可能不会。一旦我们不得不使用 DRAM,基数排序的随机访问特性将损害性能。快速排序通常需要顺序内存访问。

标签: javascript performance algorithm optimization sorting


【解决方案1】:

我测试过typed arrays,QSIP版本在现代浏览器中似乎不错:

2 000 000 个元素

          QSIP_TYPED | RDXIP_TYPED |  QSIP_STD | RDXIP_STD
----------------------------------------------------------
Chrome  |    300          1000          600        1300
Firefox |    550          1500          800        1600    

http://jsfiddle.net/u8t2a/35/

支持来源:http://caniuse.com/typedarrays):

 IE 10+   |   FF 4+  |  Chrome 7+  |  Safari 5.1+  |  Opera 11.6+   

【讨论】:

  • 在 Chrome 17 中,无论是否输入,我都会得到相同的结果——要么是测试中的错误,要么 V8 会自动进行优化。
  • @Rich - 嗯...有趣。我使用的是 v15,但我也可以在 v17 中看到相同的差异。
  • @Rich - 哦,你是说 jsperf 测试?它只有 20 000 个元素。我删除了它。它不代表大型数据集。
  • 这绝对是一个实质性的优化,以至于“支持的浏览器”警告是可以接受的。出于知识的好奇心,我希望看到一些算法级别的优化,但这肯定会回答问题的关键。
  • 对标准基数排序的影子数组使用类型化数组已经产生了迄今为止最好的结果查看我的问题编辑
【解决方案2】:

您是否考虑过多种算法组合来最大限度地利用缓存?我在基准测试中看到,当子数组变小时,您将切换到插入排序。一个有趣的方法是改用堆排序。与快速排序结合使用,它可以将最坏的情况降低到 O(nlog(n)) 而不是 O(n^2)。看看Introsort

【讨论】:

  • 我正在使用的就地快速排序(目前具有最佳性能)是避免使用堆栈进行递归。使用堆会添加另一个 ADT。如果您可以实施 introsort 并且速度更快,那将是一个很大的帮助,但我认为甚至没有令人信服的理由去尝试它。
  • 好的,我会尝试实现它并让你知道。不幸的是,我不是 javascript 方面的专家,但我会看看我能做什么......
  • @LastCoder:我在某人的 github 上找到了它。如果不太难的话,也许你可以弄清楚如何将它包含在你的基准测试中。 github.com/fatshotty/IntroSort.js/blob/master/introsort.js
【解决方案3】:

我修改了您的基准并添加了我自己的排序功能。 它的执行方式与 radixsort 相同,但它的想法(和实现)更简单,它就像一个 radixsort,但是是二进制的,所以你只有两个桶并且可以就地完成。 看http://jsfiddle.net/KaRV9/7/

我将我的代码放在“就地快速排序”的位置(因为它与快速排序非常相似,只是以其他方式选择了枢轴)。 运行它们几次,在我的 Chrome 15 中它们执行得如此接近以至于无法区分它们。

【讨论】:

  • 非常有趣,尤其是速度声明,在 FF8 中,它的 973 毫秒比标准基数排序的 172 毫秒慢了大约 6 倍。 Chrome 的 JS 引擎肯定大不相同。
  • 对于第一次运行,这里也有类似的差异,但第二次、第三次和后续运行与 radixsort 相同。不要问我为什么:-)
【解决方案4】:

我不会评论您的排序算法。你比我更了解这些。

但一个好主意是使用网络工作者。这允许您的排序在它自己的线程中在后台运行,因此不会阻塞接口。无论如何,这将是一个很好的做法。它很好地支持 Chrome 和 Firefox。 Opera 有一个非线程版本。不确定 IE 中的支持,但很容易解决这个问题。使用多个线程当然会产生开销。

合并排序可以很容易地变成一个多线程的版本,这会给你一些性能提升。消息传递当然会带来时间损失,因此它是否运行得更快取决于您的具体情况。但请记住,非阻塞特性可能会让最终用户感觉应用运行得更快。

【讨论】:

    【解决方案5】:

    编辑:我看到您已经在对较小的子数组使用插入排序。我错过了。

    快速排序在现实世界中的好方法是检查子数组的大小,如果它足够短,请使用对较大数组来说太慢的快速低开销排序,例如插入排序。

    伪代码类似于:

    quicksort(array, start, end):
      if ( (end-start) < THRESHOLD ) {
        insertion_sort(array, start, end);
        return;
      }
      // regular quicksort here
      // ...
    

    要确定阈值,您需要在您关心的平台上进行计时,在您的情况下 - 可能是不同的浏览器。测量具有不同阈值的随机数组的时间,以找到接近最优的数组。如果您发现显着差异,您也可以为不同的浏览器选择不同的阈值。

    现在,如果您的输入不是真正随机的(这很常见),您可以查看更好的枢轴选择是否可以提高性能。一个常用的方法是median of three

    【讨论】:

      猜你喜欢
      • 2017-09-14
      • 2015-06-28
      • 2011-12-17
      • 2011-10-14
      • 2012-04-27
      • 1970-01-01
      • 2016-08-30
      • 2015-09-08
      • 1970-01-01
      相关资源
      最近更新 更多