通过将 a.localeCompare(b) 切换为 (a<b?-1:(a>b?1:0)),排序速度提高 400 倍

IT技术 javascript google-chrome sorting
2021-02-21 22:54:16

通过从 javascript 排序功能切换

myArray.sort(function (a, b) {
  return a.name.localeCompare(b.name);
});

myArray.sort(function (a, b) {
  return (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0));
});

我能够将 Chrome 中大约 1700 个元素数组的排序时间从 1993 毫秒缩短到 5 毫秒。几乎是 400 倍的加速。不幸的是,这是以正确排序非英语字符串为代价的。

显然,当我尝试进行排序时,我的 UI 无法阻塞 2 秒钟。我能做些什么来避免极其缓慢的 localeCompare 但仍然保持对本地化字符串的支持?

5个回答

通过预先声明collat​​or对象并使用它的 compare 方法可以获得很大的性能改进例如:

const collator = new Intl.Collator('en', { numeric: true, sensitivity: 'base' });
arrayOfObjects.sort((a, b) => {
  return collator.compare(a.name, b.name);
});

注意:如果元素是浮动的,这将不起作用。请参阅此处的说明:Intl.Collat​​or 和带有数字选项的自然排序使用十进制数字不正确排序

这是比较 3 种方法的基准脚本:

const arr = [];
for (let i = 0; i < 2000; i++) {
  arr.push(`test-${Math.random()}`);
}

const arr1 = arr.slice();
const arr2 = arr.slice();
const arr3 = arr.slice();

console.time('#1 - localeCompare');
arr1.sort((a, b) => a.localeCompare(
  b,
  undefined, {
    numeric: true,
    sensitivity: 'base'
  }
));
console.timeEnd('#1 - localeCompare');

console.time('#2 - collator');
const collator = new Intl.Collator('en', {
  numeric: true,
  sensitivity: 'base'
});
arr2.sort((a, b) => collator.compare(a, b));
console.timeEnd('#2 - collator');

console.time('#3 - non-locale');
arr3.sort((a, b) => (a < b ? -1 : (a > b ? 1 : 0)));
console.timeEnd('#3 - non-locale');

@BradDwyer,我编辑了答案以包含基准脚本。
2021-04-24 22:54:16
@junning 我修正了第三个数组错字。它仍然如此之快。第三个测试用例的重点是概述非语言环境比较。它肯定不会返回相同的结果,但对于一些不需要语言环境的用例来说可能就足够了(例如:对英文名称列表进行排序)
2021-04-26 22:54:16
好的!我在 Chrome 69 上只慢了 15 倍,而在 localeCompare 版本上慢了 800 倍。(1.70 毫秒非语言环境,25.96 毫秒整理器,1380.65 毫秒语言环境比较)
2021-04-28 22:54:16
在 IE 11 中对特定的 500 项数组进行排序从 40 秒变为 <1 秒
2021-04-28 22:54:16
@Andy,该代码中有一个错误。测试#3 重用arr2,应该已经按照前面的测试排序了,所以第三个测试人为地更快了。最好arr.slice().sort(...)在每个测试中调用但更重要的是,#3 应该调用toLocaleLowercase()参数来进行公平的比较。否则,它会产生与前两个测试不同的排序顺序。
2021-05-01 22:54:16

我在处理 /mostly/ 拉丁字符时发现的一种有效方法是,只要两个字符串都匹配特定的正则表达式,就使用运算符。例如:/^[\w-.\s,]*$/

如果两个字符串都匹配表达式,它会快得多,而且在最坏的情况下,它似乎比盲目调用 localeCompare 稍微慢一些。

这里的例子:http : //jsperf.com/operator-vs-localecompage/11

更新:似乎 Intl.Collat​​or 目前是全面性能的最佳选择:https ://jsperf.com/operator-vs-localecompage/22

Localecompare 慢了很多数量级,以至于 toLowerCase 在很大程度上是无关紧要的。我最近重新进行了基准测试,并且如今 Intl.Collat​​or 击败了更快的正则表达式快捷方式版本。 jsperf.com/operator-vs-localecompage/22
2021-04-21 22:54:16
对我来说绝对完美,值得更多赞!我的数据集 99% 没有重音,所以你的 no_locale 正则表达式有很大的不同。
2021-04-24 22:54:16
正则表达式检测字符串是否只包含字母数字字符。\w 匹配任何字母数字字符,包括下划线。相当于 [A-Za-z0-9_]。LocaleCompare 与这些字符无关(在大多数情况下?)
2021-04-30 22:54:16
你能解释一下正则表达式的作用吗?
2021-05-01 22:54:16
LocaleCompare与字母数字字符无关,因为常规比较会将所有大写字符排序在小写字符之前。您的 jsperf 测试toLowerCase()在调用localeCompare. 这是一个无效的性能测试。使用localeCompare时不应使用toLowerCase().
2021-05-05 22:54:16

如果不查看正在排序的数据,就很难知道最快的排序。但是 jsperf 有很多很好的测试显示了排序类型之间的性能差异:http : //jsperf.com/javascript-sort/45 http://jsperf.com/sort-algorithms/31

然而,这些都没有考虑本地化字符串,我想没有简单的方法来对本地化字符串进行排序,localeCompare 可能是最好的解决方案。

查看 mozilla 参考资料说:“在比较大量字符串时,例如在对大型数组进行排序时,最好创建一个 Intl.Collat​​or 对象并使用其 compare 属性提供的函数。” https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare

但是转到 Intl.Collat​​or 参考它表明不支持 firefox/safari https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collat​​or

您可以尝试使用 localCompare 上的一些选项来加快性能。但我刚刚做了一个快速测试,改变了灵敏度水平,看起来它不会提高性能:

list.sort(function(a, b) {
  return a.localeCompare(b, {sensitivity:'base'});
});

http://jsperf.com/sort-locale-strings

>> 最好创建一个 Intl.Collat​​or 对象并使用其 compare 属性提供的功能 - 绝对同意。我进行了一些测量,是的,在 1000 行上使用 localCompare 的比较速度要高得多 16 毫秒与 25 秒
2021-05-04 22:54:16

尝试分两步排序:

  1. 与运营商:正如你所说,它会快400倍
  2. 然后用localCompare(): 现在比较少了,因为数组主要是排序的。

注意:我认为这localCompare()将主要用至少 1 个非英语字符串调用。因此,localCompare()应大大减少使用 2 个英文字符串的调用次数。

这是代码:

myArray.sort(function(a, b) {
  return (a.name < b.name ? -1 : (a.name > b.name ? 1 : 0));
});

myArray.sort(function(a, b) {
  return a.name.localeCompare(b.name);
});

该解决方案的优点是简短且易于使用。如果数组主要包含英文字符串,这将是有效的。您拥有的非英语字符串越多,第一种排序的用处就越小。但由于很容易添加到您的脚本中,因此也很容易看出这种方法是否值得。

现在如果我是你,我也会使用Intl.Collator,因为据说它比localCompare()你有很多比较要快得多

并非每个排序算法都可以利用已经排序最多的数组(有趣的是,对于非常幼稚的快速排序来说,这是一场灾难)。不知道 Javascript 中使用的那些是否可以。
2021-05-07 22:54:16

我不知道你还在寻找这个问题的解决方案

// Defaulted to ascending
// 1 asc | -1 desc
var direction = 1; 
myArray.sort(function (a, b) {
  return a.name.localeCompare(b.name) === 1 ? direction : -1 * direction;
});

=== 1在你的代码中添加了一个检查,这个改进的 perf 400x 这意味着两者都有可比的 perf 数字。

Perf 数字与 localeCompare arr 大小:3200 平均 10 次重复时间:60 毫秒

性能数字与 > 方法。平均时间花费 55 毫秒

Sry,但您的解决方案是错误的localeCompare()可能返回不同于 -1、0 或 1 的值。查看文档另外,我非常怀疑添加乘法比没有乘法要快。您应该制作 2 个比较器:一个用于升序,一个用于降序。JIT 将能够更好地内联它们。
2021-05-01 22:54:16
我不确定这是如何解决问题的。你能用你的发现做一个 jsperf 吗?===1 如何将性能提高 400 倍。
2021-05-05 22:54:16