3

On the following contract which I have tested using populus. I have experience that the same code generates different gas usage based on mapping index I used as a static value or a variable.

[Q] I really wonder does anyone else experience the same behavior of the gas change? If yes what might be the reason?

Used gas = 86658

self.list_[selfSize]     = Interval( { num: e - 1, core: c, next: self.head }); 
self.list_[selfSize + 1] = Interval( { num: 10, core: 10, next: 10 });

//self.list_[selfSize]     = Interval( { num: e - 1, core: c, next: self.head });//commented out!!
//self.list_[3]     = Interval( { num: 10, core: 10, next: 10 });//commented out!!

-- or Used gas = 65220

//self.list_[selfSize]     = Interval( { num: e - 1, core: c, next: self.head });//commented out!!
//self.list_[selfSize + 1] = Interval( { num: 10, core: 10, next: 10 });//commented out!!

self.list_[selfSize]     = Interval( { num: e - 1, core: c, next: self.head });//used GAS = 65220
self.list_[3]     = Interval( { num: 10, core: 10, next: 10 });

--As you can see both code accessing to index value 2 and 3.

Contract: Array.sol

pragma solidity ^0.4.8;
library ReceiptLib {
  struct Interval { 
    uint32 num;
    int32  core;
    uint32 next; 
  }

  struct intervalNode {
    uint32 den;       
    mapping(uint32 => Interval) list_;
    uint32 len;    
    uint32 head; 
    int32 coreLimit;
  }
  function constructReceipt(intervalNode storage self){ 
    self.head      = 1; //self.listSize - 1;
    self.coreLimit = 128;

    self.list_[0] = Interval( { num: 0, core: 0, next: 0 });
    self.list_[1] = Interval( { num: 0, core: 0, next: 0 });
    self.len = 2;
  }
  function number(intervalNode storage self, uint32 s, uint32 e, int32 c){        
    uint32   selfSize = self.len;
    //self.list_[selfSize]     = Interval( { num: e - 1, core: c, next: self.head });
    //self.list_[selfSize + 1] = Interval( { num: 10, core: 10, next: 10   });

    self.list_[selfSize]     = Interval( { num: e - 1, core: c, next: self.head  });
    self.list_[3]     = Interval( { num: 10, core: 10, next: 10 });
  }
}

contract Array{
  using ReceiptLib for ReceiptLib.intervalNode;
  ReceiptLib.intervalNode receiptList;

  function Array(){
    receiptList.constructReceipt();
  }
  function test(uint32 s, uint32 e, int32 c) {
    receiptList.number(s, e, c);
  }
}

Thank you for your valuable time and help.

  • 1
    I would suggest you test by casting literal '3' which will be uint8, to uint32 to match self.size. It may be that they are resulting in a different hash key. – o0ragman0o Mar 24 '17 at 1:54
  • 1
    The location that it's writing to in storage shouldn't make any difference to the gas cost, as long as both are uninitialized (they are). – Tjaden Hess Mar 24 '17 at 16:36
  • 1
    I just tested this in the browser compiler and they both used almost exactly the same amount of gas. Are you sure you're not mixing up the transaction gas and the execution gas? – Tjaden Hess Mar 24 '17 at 16:43
  • I am testing in populus, but since it is using solc compiler there shouldn't be any difference I assume. There is a parameter called gasUsed here. The way I use it: set_txn_hash = my_contract_1.transact().test(10, 20, 2); contract_address = unmigrated_chain.wait.for_receipt(set_txn_hash); initial_myGas = contract_address["gasUsed"]; @TjadenHess – alper Mar 25 '17 at 3:42
1

There are three factors to note about this behaviour.

self.list_[selfSize + 1] = Interval( { num: 10, core: 10, next: 10   });
...
self.list_[3] = Interval( { num: 10, core: 10, next: 10 });
  1. Even though selfSize + 1 == 3, and that self.list_[selfSize + 1] had already been initialised, you are being hit with a gas cost for initialising a full slot at self.list_[3]

  2. self.list_ is a mapping. Calculation of a mapping key is a dynamic run-time hashing operation.

    The key data is not actually stored in a mapping, only its keccak256 hash used to look up the value.

  3. That literals will be compiled to the lowest data type they will fit into, in your instance 3 is cast to uint8. Whereas self.len is uint32.

The following sha3 tests confirm that your literal mapping key self.list_[3] is indeed hashing to a different storage index than self.list_[selfSize + 1] and that the solution is to cast your literals to the intended type.

contract shaCast{
    // returns bytes32: 0x69c322e3248a5dfc29d73c5b0553b0185a35cd5bb6386747517ef7e53b15e287
    function Sha3Lit() constant returns (bytes32){
        return sha3(3);
    }

    // returns bytes32: 0x3ef5a213fe22de1cea28d60f518364ff95fe0b73660793e3efcfbe31bda68aec
    function Sha3LitCast() constant returns (bytes32){
        return sha3(uint32(3));
    }

    //returns bytes32: 0x3ef5a213fe22de1cea28d60f518364ff95fe0b73660793e3efcfbe31bda68aec
    function Sha3Uint32() constant returns (bytes32) {
            uint32 n = 3;
            return sha3(n);
    }
}
  • Thanks a lot sir. I tried following: self.list_[uint32(3)] = Interval( { num: 10, core: 10, next: 10 }); but still its used gas remain on 65235. @o0ragman0o – alper Mar 24 '17 at 9:36
  • self.list_[uint8(selfSize + 1)] = Interval( { num: 10, core: 10, next: 10 }); used gas also remain on 86639. I guess problem is related to some other issue. @o0ragman0o – alper Mar 24 '17 at 9:38
  • 1
    Running your code in Browser Solidity Javascript VM, I get similar gas costs ( 84113 gas) for both self.list_[selfSize + 1] = Interval( { num: 10, core: 10, next: 10 }); and self.list_[bytes32(3)] = Interval( { num: 10, core: 10, next: 10 }); – o0ragman0o Mar 25 '17 at 2:52
  • I get similar gas costs even without the changes, I think OP may have just read the tx gas cost for one and the execution cost for the other – Tjaden Hess Mar 25 '17 at 3:20
  • 2
    The transaction gas cost is the cost for the whole transaction, including the 21k gas from the transaction itself, plus the data from sending the arguments. Th "execution cost" is what a contract would pay if it called the function – Tjaden Hess Mar 25 '17 at 3:57
1

Have self.list_[2] and self.list_[3] already been set when you write them? If you're overwriting an existing key and clearing some old data in the process, that should be cheaper than allocating fresh storage.

  • selfSize + 1 's result is not stored in a variable it was just generated.@Edmund Edgar. – alper Mar 23 '17 at 9:08
  • Please note that on my question 2 is replaced with selfSize.@Edmund Edgar. – alper Mar 23 '17 at 9:10
  • Why a one addition cost around 20,000 more gas also? @Edmund Edgar – alper Mar 23 '17 at 11:43

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.