Skip to content
This repository was archived by the owner on Apr 12, 2021. It is now read-only.

Optimize loop iterator in Lib_MerkleTree.sol.#354

Merged
maurelian merged 1 commit intoethereum-optimism:masterfrom
cleanunicorn:master
Mar 31, 2021
Merged

Optimize loop iterator in Lib_MerkleTree.sol.#354
maurelian merged 1 commit intoethereum-optimism:masterfrom
cleanunicorn:master

Conversation

@cleanunicorn
Copy link
Copy Markdown
Contributor

Description
While reviewing the code from a project that uses the same Lib_MerkleTree library you're using, I noticed a sub-optimal loop iterator using uint8 instead of uint256.

Because of this, Solidity will generate more operations to make sure the iterator stays within the uint8 constraints, hence it will cost more gas to execute the loop.

I created a Solidity contract and ran a few tests which might help you decide if you want to include the change or not.

The following Solidity contract example was used to measure the difference between using uint8 and uint256 with a few example inputs.

contract FindBit {
    function find_highest_set_bit(
        uint value
    ) 
        public 
        pure 
        returns (
            uint
        ) 
    {
        uint _in = value;
        
        if (_in == 1) {
            return 0;
        }

        // Find the highest set bit (will be floor(log_2)).
        // Borrowed with <3 from https://github.com/ethereum/solidity-examples
        uint256 val = _in;
        uint256 highest = 0;
        for (uint8 i = 128; i >= 1; i >>= 1) {
            if (val & (uint(1) << i) - 1 << i != 0) {
                highest += i;
                val >>= i;
            }
        }

        // Increment by one if this is not a perfect logarithm.
        if ((uint(1) << highest) != _in) {
            highest += 1;
        }

        return highest;        
    }
}

Changing the line from uint8 to uint256

        for (uint8 i = 128; i >= 1; i >>= 1) {
        for (uint256 i = 128; i >= 1; i >>= 1) {

Gives the following different gas costs, while returning the same, identical results.

Solidity version 0.8.3+commit.8d00100c and 200 optimization runs were used in Remix IDE for tests.

input output uint8 gas uint256 gas difference
8 3 1876 1692 184
15 4 1951 1767 184
100 7 1951 1767 184
128 7 1980 1779 201
4294967295 (0xffffffff) 32 2263 2028 235
1099511627775 (0xffffffffff) 40 2159 1941 218
18446744073709552000 (0xffffffffffffffff) 64 2367 2115 252

Additional context

The Solidity example repository includes this method as an example for other developers to learn how to use the language. Because it does not strive to be the optimal implementation, its purpose is to show different features of the language itself.

https://github.com/ethereum/solidity-examples/blob/f44fe3b3b4cca94afe9c2a2d5b7840ff0fafb72e/src/bits/Bits.sol#L87-L99

Copy link
Copy Markdown
Collaborator

@maurelian maurelian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @cleanunicorn thanks for the thorough details in the submission.

This could save us quite a bit of gas in the long haul!

LGTM, but would appreciate one more quick sanity check from either @smartcontracts or @ben-chain.

Copy link
Copy Markdown
Collaborator

@smartcontracts smartcontracts left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Great catch. Thank you!!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants