We are pleased to announce a new package, sort, available in mops. It is meant to bundle various types of sorting algorithms in one package and is optimised for performance. The initial version contains bucket sort, radix sort and merge sort for keys of type Nat32.
All algorithms work on VarArray input and are in-place sorts.
The implementations outperform all previously available sort algorithms in Motoko in terms of instructions and memory (see benchmarks further below).
As counting based algorithms, bucket sort and radix sort require keys with bits.
The package currently requires keys to be of type Nat32. Versions for other key types such as Nat64, Int32/64 or Blob are planned for the future based on demand. Please request what you need.
Merge sort is comparison based but at this point is also only implemented for key type Nat32 in this package. For merge sort with arbitrary keys, requiring only a compare function, see this motoko-core PR instead: Optimize sort. by AStepanov25 · Pull Request #453 · caffeinelabs/motoko-core · GitHub The PR replaces VarArray.sort in motoko-core with the merge sort of this package, re-written for a generic compare function. The PR is about twice as fast while using only half the memory as core’s old VarArray.sort.
Which algorithm should you use? Radix sort does not have a worst case (or worst case = average case). But bucket sort is faster in the average case. Merge sort is slower but uses the least amount of memory. Both bucket sort and radix sort internally fall back on merge sort for small input, so there is no need to do that manually.
Some recommendations:
- Use radix sort when the distribution of keys in the input is unknown or known to be highly non-uniform. It’s speed is independent of the distribution.
- Use radix sort if tight control of the worst case performance is required. For example, if the key distribution in the input can be controlled by an adversary.
- Use bucket sort if the distribution is known to be close to uniform.
- Use bucket sort for best performance in the average case.
Optional: for bucket sort and radix sort, if the full key space isn’t used then you can speed up the sort by passing in an upper bound for the keys. In some cases it may be worth calculating the maximum key value by doing one extra pass over data before calling sort.
The bucket sort implementation was inspired by motoko-sort (also see this forum thread). We improved instructions a little bit and memory by a lot. But motoko-sort provides many key types where we currently only support Nat32.
For further development of this package we would like know how sorting is being used in canisters. Please let us know below. For example:
- Are you sorting call input (arguments) or data that is already in state before the call?
- Are you sorting output (preparing return values) or data to keep it in state in a sorted way.
- What sizes of input are sorting?
- What are your key types?
- What is your desired input/output type (VarArray, Array, List, etc.)?
The algorithms are described further in the README.
Benchmarks:
Instructions:
| 100 | 1000 | 10000 | 12000 | 100000 | 1000000 | |
|---|---|---|---|---|---|---|
| bucketSort | 43_946 | 0.42 M | 4.17 M | 4.96 M | 41.3 M | 412 M |
| bucketSortWorstCase | 0.21 M | 1.15 M | 9.41 M | 11.1 M | 91.9 M | 553 M |
| radixSort | 66_439 | 1.04 M | 8.82 M | 10.5 M | 63.9 M | 527 M |
| Zhus | 63_420 | 0.62 M | 6.3 M | 7.53 M | 63 M | 628 M |
| mergeSort | 66_620 | 1.04 M | 15.4 M | 18.5 M | 193 M | 2_319 M |
| VarArray | 0.21 M | 2.68 M | 35.8 M | 43.1 M | 442 M | 5_047 M |
Memory:
| 100 | 1000 | 10000 | 12000 | 100000 | 1000000 | |
|---|---|---|---|---|---|---|
| bucketSort | 872 B | 5.24 KiB | 55.4 KiB | 63.21 KiB | 518.96 KiB | 4.82 MiB |
| bucketSortWorstCase | 1.54 KiB | 8.27 KiB | 72.41 KiB | 80.23 KiB | 646.99 KiB | 4.88 MiB |
| radixSort | 536 B | 2.28 KiB | 47.44 KiB | 55.25 KiB | 647 KiB | 4.07 MiB |
| Zhus | 1.52 KiB | 12.06 KiB | 117.53 KiB | 140.97 KiB | 1.14 MiB | 11.44 MiB |
| mergeSort | 536 B | 2.28 KiB | 19.86 KiB | 23.77 KiB | 195.64 KiB | 1.91 MiB |
| VarArray | 736 B | 4.23 KiB | 39.39 KiB | 47.2 KiB | 390.95 KiB | 3.82 MiB |