I have tried to implement a simple cellular automaton logic using two different ways to track the final state:
- Track every iteration in a multi-dimensional array.
- Store only the last two iterations.
Since updates are cheaper than storing new elements I expected the second contract to be more cost efficient, but it is not.
Below is the code for both versions. The function being benchmarked is advance
. The way I measure costs is just by looking at the Transaction Cost
value in the Remix output.
Contract V1:
contract Rule90 {
bool[][] state;
constructor() {
state.push([false,false,true,false,false]);
}
function newState(bool a, bool b, bool c) private pure returns (bool y) {
//...
}
function advance() public {
uint iter = state.length;
bool[] memory last = state[iter-1];
uint newLength = last.length+2;
bool[] memory next = new bool[](newLength);
for (uint i=1; i < last.length-1; i++) {
next[i+1] = newState(last[i-1], last[i], last[i+1]);
}
state.push(next);
}
}
Contract V2:
contract Rule90 {
bool[] s1;
bool[] s2;
bool useS1 = false;
constructor() {
s1 = [false, false,true,false,false];
s2 = [false, false];
}
function newState(bool a, bool b, bool c) private pure returns (bool y) {
//...
}
function advance() public {
if (useS1) {
advanceState(s1, s2);
} else {
advanceState(s2, s1);
}
useS1 = !useS1;
}
function advanceState(bool[] storage current, bool[] storage prev) private {
bool x;
uint n = current.length;
for (uint i=1; i<prev.length-1; i++) {
x = newState(prev[i-1], prev[i], prev[i+1]);
if (i+1 > n-1) {
current.push(x);
} else {
current[i+1] = x;
}
}
current.push(false);
current.push(false);
}
}
There are additional two push
instructions in the Contract V2 compared to creating a fixed memory array and at the end appending it to a multi-dimensional array.
Any ideas why the second contract costs more?
Update
Gas prices plot where x axis is iteration and y axis cost of calling advance
method: