7

Mind the following code:

// This is expensive
obj.a = uint32(obja);
obj.b = uint32(objb);
obj.c = uint32(objc);
objs[objName] = obj;

// This is much cheaper
objs[objName].a = uint32(obja);
objs[objName].b = uint32(objb);
objs[objName].c = uint32(objc);

Where obj is a memory struct of type

struct Obj {
    uint32 a;
    uint32 b;
    uint32 c;
}

And obja, objb and objc are uint32s. Why the first version is more expensive if, in theory, it could be done in a single SSTORE call, whereas the latter would require 3?

  • Just to be sure, you are declaring Obj memory obj, correct? i.e. your're actually using a memory struct, not a storage one as a temporary variable. – Tjaden Hess Dec 1 '16 at 15:02
  • @TjadenHess yep that is correct, I had Obj memory obj = objs[objName] earlier on. – MaiaVictor Dec 1 '16 at 15:03
  • So, it looks like if you use a single global Obj variable, then it is actually cheaper to use the memory route, but when you use an array of them, then it becomes more expensive. Similarly with mappings. – Tjaden Hess Dec 1 '16 at 15:07
  • Also, note that setting a storage variable from a zero value to nonzero is more expensive than updating a nonzero variable. So the order in which you do this test matters. I'd recommend initializing the struct of interest to have all nonzero values before looking at gas costs – Tjaden Hess Dec 1 '16 at 15:11
  • Looking at the debugger, the memory one seems to use 14 SSTOREs, for some reason. As does the straight storage one. – Tjaden Hess Dec 1 '16 at 15:16
1

I think you've run into the trap that initializing a storage slot (changing from 0 to something else) for the first time costs 20000 gas and rewriting an already initialized storage slot later costs only 5000 gas. If you repeat your test a second time for the same objName, you'll notice the difference.

Setting via memory obj will be slightly (~100-200 gas) cheaper.

The most efficient way would be:

function set(bytes32 objName, uint obja, uint objb, uint objc) public {
    Obj storage obj = objs[objName];

    obj.a = uint32(obja);
    obj.b = uint32(objb);
    obj.c = uint32(objc);
}

in theory, it could be done in a single SSTORE call, whereas the latter would require 3?

A SSTORE can only set one 32-byte storage slot at once. If you have 3 x uint32, you'll have 3 SSTORE calls.

0

You haven't provided anything to back up your measurements, so here is a setup which proves the opposite:

MyContract.sol:

pragma solidity 0.4.24;

contract MyContract {
    struct Obj {
        uint32 a;
        uint32 b;
        uint32 c;
    }

    mapping(string => Obj) internal objs;

    function set1(string objName, uint256 obja, uint256 objb, uint256 objc) external {
        Obj memory obj;
        obj.a = uint32(obja);
        obj.b = uint32(objb);
        obj.c = uint32(objc);
        objs[objName] = obj;
    }

    function set2(string objName, uint256 obja, uint256 objb, uint256 objc) external {
        objs[objName].a = uint32(obja);
        objs[objName].b = uint32(objb);
        objs[objName].c = uint32(objc);
    }
}

MyContractTest.js:

contract("MyContractTest", function(accounts) {
    it("performance measurement:", async function() {
        let myContract = await artifacts.require("MyContract.sol").new();
        for (let a = 0; a < 10; a++) {
            for (let b = 0; b < 10; b++) {
                for (let c = 0; c < 10; c++) {
                    let set1gas = await myContract.set1.estimateGas("test", a, b, c);
                    let set2gas = await myContract.set2.estimateGas("test", a, b, c);
                    console.log(`set1gas = ${set1gas}, set2gas = ${set2gas}`);
                }
            }
        }
    });
});

You can run this via truffle test MyContractTest.js, and observe that the first method is in fact around 10K gas units cheaper than the second method.

As mentioned in the first answer, you may have experienced the same issue as when conducting performance (speed) measurement without taking into consideration the impact of the current state of the cache memory (which affects the caching heuristics applied by the underlying platform during runtime).

In a sense, measuring gas consumption on the block-chain is similar to measuring time performance on "standard" machines.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.