Skip to content

Commit 57a20e9

Browse files
authored
⚡️ Optimize sort even further (ethereum#10)
2 parents 489efca + ee25d4e commit 57a20e9

File tree

2 files changed

+43
-29
lines changed

2 files changed

+43
-29
lines changed

.gas-snapshot

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ SafeTransferLibTest:testTransferWithMissingReturn() (gas: 36761)
6767
SafeTransferLibTest:testTransferWithNonContract() (gas: 3012)
6868
SafeTransferLibTest:testTransferWithReturnsTooMuch() (gas: 37164)
6969
SafeTransferLibTest:testTransferWithStandardERC20() (gas: 36770)
70-
SortTest:testSortAddressesPsuedorandom() (gas: 94679)
71-
SortTest:testSortAddressesPsuedorandomBrutalizeUpperBits() (gas: 110828)
72-
SortTest:testSortAddressesReversed() (gas: 51760)
73-
SortTest:testSortAddressesSorted() (gas: 46390)
74-
SortTest:testSortBasicCase() (gas: 1165)
70+
SortTest:testSortAddressesPsuedorandom() (gas: 91269)
71+
SortTest:testSortAddressesPsuedorandomBrutalizeUpperBits() (gas: 108051)
72+
SortTest:testSortAddressesReversed() (gas: 49771)
73+
SortTest:testSortAddressesSorted() (gas: 44963)
74+
SortTest:testSortBasicCase() (gas: 1088)
7575
SortTest:testSortOriginalPsuedorandom() (gas: 222863)
7676
SortTest:testSortOriginalReversed() (gas: 162509)
7777
SortTest:testSortOriginalSorted() (gas: 148467)
78-
SortTest:testSortPsuedorandom() (gas: 88103)
79-
SortTest:testSortReversed() (gas: 45139)
80-
SortTest:testSortSorted() (gas: 39771)
78+
SortTest:testSortPsuedorandom() (gas: 84693)
79+
SortTest:testSortReversed() (gas: 43150)
80+
SortTest:testSortSorted() (gas: 38344)
8181
SortTest:testSortTestOverhead() (gas: 37516)

src/utils/Sort.sol

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,50 @@ library Sort {
2323
mstore(a, 0) // For insertion sort's inner loop to terminate.
2424

2525
// Let the stack be the start of the free memory.
26-
let stackBottom := mload(0x40)
27-
let stack := add(stackBottom, 0x40)
26+
let stack := mload(0x40)
27+
let stackBottom := stack
2828

29-
{
29+
if iszero(lt(n, 2)) {
3030
// Push `l` and `h` to the stack.
3131
// The `shl` by 5 is equivalent to multiplying by `0x20`.
3232
let l := add(a, 0x20)
33-
let h := add(a, shl(5, n))
34-
mstore(stackBottom, l)
35-
mstore(add(stackBottom, 0x20), h)
33+
let h := add(l, shl(5, n))
3634

37-
let s := 0 // Number of out of order elements.
3835
let u := mload(l) // Previous slot value, `u`.
36+
let j := add(l, 0x20)
37+
let s := 0 // Number of out of order elements.
38+
3939
// prettier-ignore
40-
for { let j := add(l, 0x20) } iszero(gt(j, h)) { j := add(j, 0x20) } {
40+
for {} 1 {} {
4141
let v := mload(j) // Current slot value, `v`.
4242
s := add(s, gt(u, v)) // Increment `s` by 1 if out of order.
4343
u := v // Set previous slot value to current slot value.
44+
j := add(j, 0x20)
45+
// prettier-ignore
46+
if iszero(lt(j, h)) { break }
47+
}
48+
49+
// If the array is not sorted or reverse sorted,
50+
// push `l` and `h` onto the `stack`.
51+
if iszero(or(iszero(s), eq(add(s, 1), n))) {
52+
mstore(stack, l)
53+
mstore(add(stack, 0x20), sub(h, 0x20))
54+
stack := add(stack, 0x40)
4455
}
45-
// If the array is sorted, or reverse sorted,
46-
// subtract `0x40` from `stack` to make it equal to `stackBottom`,
47-
// which skips the sort.
48-
// `shl` 6 is equivalent to multiplying by `0x40`.
49-
stack := sub(stack, shl(6, or(iszero(s), eq(add(s, 1), n))))
5056

5157
// If 50% or more of the elements are out of order,
5258
// reverse the array.
5359
if iszero(lt(shl(1, s), n)) {
60+
h := sub(h, 0x20)
5461
// prettier-ignore
55-
for {} lt(l, h) {} {
62+
for {} 1 {} {
5663
let t := mload(l)
5764
mstore(l, mload(h))
5865
mstore(h, t)
5966
h := sub(h, 0x20)
6067
l := add(l, 0x20)
68+
// prettier-ignore
69+
if iszero(lt(l, h)) { break }
6170
}
6271
}
6372
}
@@ -72,10 +81,17 @@ library Sort {
7281
let l := mload(stack)
7382
let h := mload(add(stack, 0x20))
7483

75-
// Do insertion sort if `h - l < 0x20 * 16`.
76-
if iszero(shr(9, sub(h, l))) {
84+
// Do insertion sort if `h - l <= 0x20 * 12`.
85+
// Threshold is fine-tuned via trial and error.
86+
if iszero(gt(sub(h, l), 0x180)) {
87+
// Hardcode sort the first 2 elements.
88+
let t := mload(add(l, 0x20))
89+
if iszero(lt(mload(l), t)) {
90+
mstore(add(l, 0x20), mload(l))
91+
mstore(l, t)
92+
}
7793
// prettier-ignore
78-
for { let i := add(l, 0x20) } iszero(gt(i, h)) { i := add(i, 0x20) } {
94+
for { let i := add(l, 0x40) } iszero(gt(i, h)) { i := add(i, 0x20) } {
7995
let k := mload(i) // Key.
8096
let j := sub(i, 0x20) // The slot before the current slot.
8197
let v := mload(j) // The value of `j`.
@@ -134,15 +150,13 @@ library Sort {
134150
{
135151
// We can skip `mstore(stack, l)`.
136152
mstore(add(stack, 0x20), p)
137-
// `shl` 6 is equivalent to multiplying by `0x40`.
138-
stack := add(stack, shl(6, gt(p, l)))
153+
stack := add(stack, mul(0x40, gt(p, l)))
139154
}
140155
// If slice on right of pivot is non-empty, push onto stack.
141156
{
142157
mstore(stack, add(p, 0x20))
143158
mstore(add(stack, 0x20), h)
144-
// `shl` 6 is equivalent to multiplying by `0x40`.
145-
stack := add(stack, shl(6, lt(add(p, 0x20), h)))
159+
stack := add(stack, mul(0x40, lt(add(p, 0x20), h)))
146160
}
147161
}
148162
mstore(a, n) // Restore the length of `a`.

0 commit comments

Comments
 (0)