Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use Yul for mem ops in Array.sort
  • Loading branch information
CodeSandwich committed Jul 1, 2022
commit fcbb75082b2b75a33cd2034fab4edde3e88de66c
48 changes: 31 additions & 17 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,22 @@ library Arrays {
*
* Sorting is done in-place using the heap sort algorithm.
* Examples of gas cost with optimizer enabled for 200 runs:
* - 10 random items: ~11K gas
* - 100 random items: ~224K gas
* - 10 random items: ~8K gas
* - 100 random items: ~156K gas
*/
function sort(uint256[] memory array) internal pure {
unchecked {
uint256 length = array.length;
if (length < 2) return;
// Heapify the array
for (uint256 i = length / 2; i-- > 0; ) {
_siftDown(array, length, i, array[i]);
_siftDown(array, length, i, _arrayLoad(array, i));
}
// Drain all elements from highest to lowest and put them at the end of the array
while (--length != 0) {
uint256 val = array[0];
_siftDown(array, length, 0, array[length]);
array[length] = val;
uint256 val = _arrayLoad(array, 0);
_siftDown(array, length, 0, _arrayLoad(array, length));
_arrayStore(array, length, val);
}
}
}
Expand All @@ -79,32 +79,46 @@ library Arrays {
function _siftDown(
uint256[] memory array,
uint256 length,
uint256 empty,
uint256 emptyIdx,
uint256 inserted
) private pure {
unchecked {
while (true) {
// The first child of empty, one level deeper in the heap
uint256 child = empty * 2 + 1;
uint256 childIdx = (emptyIdx << 1) + 1;
// Empty has no children
if (child >= length) break;
uint256 childVal = array[child];
uint256 otherChild = child + 1;
if (childIdx >= length) break;
uint256 childVal = _arrayLoad(array, childIdx);
uint256 otherChildIdx = childIdx + 1;
// Pick the larger child
if (otherChild < length) {
uint256 otherChildVal = array[otherChild];
if (otherChildIdx < length) {
uint256 otherChildVal = _arrayLoad(array, otherChildIdx);
if (otherChildVal > childVal) {
child = otherChild;
childIdx = otherChildIdx;
childVal = otherChildVal;
}
}
// No child is larger than the inserted value
if (childVal <= inserted) break;
// Move the larger child one level up and keep sifting down
array[empty] = childVal;
empty = child;
_arrayStore(array, emptyIdx, childVal);
emptyIdx = childIdx;
}
array[empty] = inserted;
_arrayStore(array, emptyIdx, inserted);
}
}

function _arrayLoad(uint256[] memory array, uint256 idx) private pure returns (uint256 val) {
/// @solidity memory-safe-assembly
assembly {
val := mload(add(32, add(array, shl(5, idx))))
}
}

function _arrayStore(uint256[] memory array, uint256 idx, uint256 val) private pure {
/// @solidity memory-safe-assembly
assembly {
mstore(add(32, add(array, shl(5, idx))), val)
}
}
}
2 changes: 1 addition & 1 deletion test/utils/Arrays.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ contract('Arrays', function (accounts) {
});
});

describe('sort', function () {
describe.only('sort', function () {
let arraysImpl;

before(async function () {
Expand Down