1

Let's consider the following completely useless code (tested in Remix):

// solium-disable linebreak-style
pragma solidity ^0.8.0;

contract MyContract
{
    struct TestVars
    {
        uint8 a;
        uint64 b;
        uint64 c;
    }
    
    TestVars private vals;
    
    constructor()
    {
        vals.a = 5;
        vals.b = 6;
        vals.c = 7;
    }
    
    function readValsStorage()
        external 
    {
        TestVars storage v = vals;
        uint a = v.a;
        uint b = v.b;
        uint c = v.c;
    }
    
    function readValsMemory()
        external 
    {
        TestVars memory v = vals;
        uint a = v.a;
        uint b = v.b;
        uint c = v.c;
    }
}

The thing we are into is gas cost. It wonders me why storage is cheaper. I have read that each read from the contract storage costs gas. Also if you pack data into struct it behaves as one field unless you exceed the total size over 2^256.

So now my thougts come: if I read a, b, c from storage it will read 3 fields, so it will cause triple gas cost. It should be cheaper to read the entire struct to the memory as one field and grab variables.

But the result was opposite - the storage is cheaper. Why?

1

Possibly a kind soul with more knowledge of v8 compiler optimization will chime in. This will be a general observation.

storage is cheaper. Why?

That's a misleading conclusion because you read from storage in both cases.

TestVars storage v = vals;
TestVars memory v = vals;

What you do with it (left side) differs. In the storage case, you set a storage pointer to the slot where vals values can be found and nothing else has happened, yet.

In the memory case, you have copied the struct values from the storage location to v, in memory. This is more work than the first case.

As you carry on with the assignments to uint a, b, c you are copying from storage (for the first time), or from memory (again).

The assignments to storage (SSTORE) in the constructor are the most expensive operations in the contract, by a wide margin, followed by the SREAD operations but both variations of your test read the same region.

You can see a little bit of a difference with this effort:

    function readValsMemory()
        external 
    {
        TestVars memory vals2 = TestVars ({
          a: 5,
          b: 6,
          c: 7
        });
        uint a = vals2.a;
        uint b = vals2.b;
        uint c = vals2.c;
    }

It is cheaper overall because it doesn't touch storage. The difference might not seem tremendous but keep in mind that it sets before it gets and still wins.

Hope it helps.

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.