Add optional precompilation pass to optimize order of declarations

Summary

In the example contract below, reversing the order of declarations of assetCount and assets costs an extra 1136 gas to deploy (159329 vs 160401). The expected behavior is that the order of declarations should not matter, or to the extent that it does, the Solidity compiler will automatically choose the order resulting in the lowest gas cost to deploy.

Example contract

pragma solidity ^0.4.24;
contract Storer {

    struct storedAsset {
        string          asset;
        string          description;
        string          place;
        uint            whenStored;
        bool            secured;
    }

    uint32 public assetCount;
    mapping(uint32 => storedAsset) assets;
    event UpdatedAssetCount(uint32 indexed oldAssetCount, uint32 indexed newAssetCount);

    function newAssets(uint32 newCount) public {
        emit UpdatedAssetCount(assetCount, (assetCount + newCount));
        assetCount += newCount;
    }
   //other functions, events, security, etc. removed to more clearly demonstrate issue
}

Note that if the event is removed from the contract, the difference between the two declaration orders is 536 gas (134311 vs 134847). It is further unexpected that the presence of the event changes the size of the deployment gas cost difference between the ordering of two other state variable declarations (which is not expected to exist at all).

The specific naming of struct fields also makes a difference. For example, changing the name of the boolean field from “secured” to “latched” (both seven characters long) changes the difference in gas cost for that swapped declaration order to 1008 (159329 vs 160337).

The numbers work out differently when using uint instead of uint32, but the gist of the issue still holds.

The feature needs to be optional because the order of variable declaration impacts the storage slot and advanced developers may need to control that for certain patterns (around upgradability etc). Like the ability to drop into assembly, the advanced-developer case should be the exception, not the default.

This might also require identifying the state variable being referenced most often, and assigning that the storage slot with the fewest nonzero bytes (namely, 0) with other commonly-used variables getting slot numbers with more zero bytes as well, with the state variable being referenced n-th most often being assigned the constant with the n-th fewest nonzero bytes, as this slot number is reused as a constant and gas costs depend on the count of nonzero bytes.

Having to optimize storage for these sort of assembly-level considerations (switching the order of declaration of variables, including for more efficient packing) does not seem to be consistent with the image of Solidity as a relatively high-level language (which is created in part by the first sentence of official docs). Given that in smart contracts, these optimizations or lack thereof have measurable and maybe nontrivial costs, there is value in doing them.

I would like to see a precompilation pass added to the Solidity compiler, on by default but possible to disable with an option when running compilation, which reorders elements to optimize gas usage. I think this could make Ethereum overall more efficient.

These are assembly-level efficiency issues that should be properly addressed by a compiler. Further, assembly-level efficiency considerations present a greater direct cost to users in Ethereum than in many general-purpose computing environments, where failure to optimize efficiency may be less costly. In this case, compiler efficiency is not just a direct cost to user of the code, but also something that impacts the entire Ethereum community because if we could deliver the same functional value at lower gas costs, we can reduce network congestion and thereby reduce costs associated for transacting on or storing the blockchain even in other applications.

Requiring developers to get into Assembly details for code performance, even when using the optimizer in their compilation workflow (solc --optimize) does not seem to be consistent with the docs’ description of Solidity as a relatively high-level language.

The existing optimizer works on the compiled assembly, after compilation. An additional optimizer could act prior to compilation, doing things like reordering variable declaration to deliver the same functionality but more efficiently. This thread suggests the creation of a new precompilation optimizer for ordering variable (and maybe function) declarations to support more efficient packing and referencing.

great. thanks
it was helpful for me