My goal is to iterate around 100 items or even more inside a mapped data stuct per a function call by using minimum gas
. In order to achieve it I have used two different approaches.
In my case only empty function call costs 23499 Gas.
I have observed that to read a single mapped items costs around 303 gas
<= (23802 – 23499) and a single push costs 25979 Gas
<= (49478 – 23499) for the following code:
struct Interval {
uint32 num;
int32 core;
uint32 next;
}
Interval[] list;
function createList(){
for(int i; i < 100; i++)
list.push(Interval( { num: 10, core: 10, next: 10 }) );//some dummy push
//to push a item costs 25979 gas. So 100 items => 100 x 25979 = 2597900 gas.
}
}
function iterateList(){
for(int i; i < 100; i++){
list[i];
//to read the list per item costs 303 gas. So 100 items => 100 x 303 = 30300 gas.
}
}
function singlePush(){ //costs 49478 Gas
list.push(Interval( { num: 30, core: 10, next: 10 }) );
}
function singleRead(){ //costs 25979 Gas
list[0];
}
function emptyCall(){ //costs 23524 Gas
}
[Q] Why reading data is expensive? If I use assembly code to access to data would gas
cost be cheaper?
When I update the code as follows(changing Interval[] list;
with mapping(int32 => Interval) list;
)… gas
amount for reading and pushing data decreases dramatically. Now I have observed that to read a single mapped items costs around 25 gas
<= (23524 – 23499) and single push costs 20545 Gas
<= (44044 – 23499).
struct Interval {
uint32 num;
int32 core;
uint32 next;
}
mapping(int32 => Interval) list;
function createList(){
for(int i; i < 100; i++)
list[i] = Interval( { num: 10, core: 10, next: 10 });
//to push a item costs 20545 gas. So 100 items => 100 x 25979 = 2054500 gas.
}
}
function iterateList(){
for(int i; i < 100; i++){
list[i];
//to access into list per item costs around 25 gas. So 100 items => 100 x 25 = 2500 gas.
}
}
function singlePush(){ //costs 23524 Gas.
list[0] = Interval( { num: 10, core: 10, next: 10 });
}
function emptyCall(){ //costs 23499 Gas
}
[Q] What may cause to have dramatic change on gas to read and push data based on the definition differ as follows: mapping(int32 => Interval) list
vs Interval[] list
.
I did ask multiple questions all in once but I believe that they all linked to each other.
Best Answer
A
mapping
is a namespace with 2^256 "slots" and no index. Items are stored in the namespace by index. You can think of it as a table, with 2^256 rows initialized to0
values because it return0
if nothing else was explicitly written to a given slot that gets inspected.Deep down, a dynamic array is also organized this way but first it has a
uint
value forlength
. Each index is auint
<=length
and if one inquires about a value out of range, it will revert.To append to a dynamic array, first, the length is increased (
length++
), then the value is written to the new slot, so a read and two writes. For fixed-size arrays, the length is not adjusted but the range check still applies.SSTORE writes a 32-byte word to storage, always in a slot in the Patricia trie at an address that is computed from storage layout for the contract. SSTORE costs are not consistent, so it is good to be aware of variance. The same function will produce different results depending on input and context.
As of Constantinople:
So, the first time you
list.push()
, the length was0
and then is not0
, so expect 20,000 gas for that part of thepush
plus another 20,000 to write a non-zero value. Later, the next length revision will cost 5,000 gas. Or, you canpush(0)
and that will be different again.Mappings have no
length
to maintain, so they will always be cheaper for similar comparisons keeping the above-noted variances in mind.Hope it helps.