Search code examples
javascriptgoogle-chromesorting

400x Sorting Speedup by Switching a.localeCompare(b) to (a<b?-1:(a>b?1:0))


By switching a javascript sort function from

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

to

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

I was able to cut the time to sort a ~1700 element array in Chrome from 1993 milliseconds to 5 milliseconds. Almost a 400x speedup. Unfortunately this is at the expense of correctly sorting non-english strings.

Obviously I can't have my UI blocking for 2 seconds when I try to do a sort. Is there anything I can do to avoid the horribly slow localeCompare but still maintain support for localized strings?


Solution

  • A great performance improvement can be obtained by declaring the collator object beforehand and using it's compare method. EG:

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

    NOTE: This doesn't work ok if the elements are floats. See explanation here: Intl.Collator and natural sort with numeric option sorts incorrectly with decimal numbers

    Here's a benchmark script comparing the 3 methods:

    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');