kadenzipfel / gas-optimizations

List of smart contract optimizations in solidity
610 stars 61 forks source link

Conditional statement "!=" is not more gas efficient than ">" or ">=" #23

Open PaulRBerg opened 1 year ago

PaulRBerg commented 1 year ago

Alleged Gas Costs

At the time of opening the issue, the Optimal Comparison Operator document makes the following claim:

In the case of a conditional statement, it is further optimal to use != when possible.

And then, the following gas costs are provided:

// 164 gas
function d() external pure {
  require(1 > 0);
}

// 208 gas
function e() external pure {
  require(1 >= 0);
}

// 120 gas
function f() external pure {
  require(1 != 0);
}

Actual Gas Costs

In the first example below, each function execution will consume exactly 21,162 gas (with the function compare per se consuming exactly 98 gas).

Try these on Remix with the optimizer enabled and set to 200 runs

1. Require example ```solidity pragma solidity =0.8.17; contract A { function compare() external pure { require(1 > 0); } } contract B { function compare() external pure { require(1 >= 0); } } contract C { function compare() external pure { require(1 != 0); } } ```
2. If/ else example And in this case, the reported `gasUsed` will be 7. ```solidity pragma solidity =0.8.17; contract A { function compare(uint256 x) external view returns (uint256 gasUsed) { uint256 startGas = gasleft(); if (x >= 0) { uint256 foo = 1 + 2; foo; } gasUsed = startGas - gasleft(); } } contract B { function compare(uint256 x) external view returns (uint256 gasUsed) { uint256 startGas = gasleft(); if (x > 0) { uint256 foo = 1 + 2; foo; } gasUsed = startGas - gasleft(); } } contract C { function compare(uint256 x) external view returns (uint256 gasUsed) { uint256 startGas = gasleft(); if (x != 0) { uint256 foo = 1 + 2; foo; } gasUsed = startGas - gasleft(); } } ```

Possible Explanations

Let's start with this is what definitely isn't - this is not a matter of enabling the optimizer. Even with the optimizer disabled, the > 0 and != 0 checks cost the same in Solidity v0.8.17. That leaves with a couple of options left:

  1. You defined all functions in the same contract, which made the 4-byte function signature add extra gas, depending upon the results of the keccak256 hsah.
  2. Older compiler had different gas costs for these comparison operations? Unlikely, but possible.
  3. Some other unintentional benchmarking/ instrumenting mistake.

Whatever the case, as discussed in https://github.com/kadenzipfel/gas-optimizations/issues/3 and https://github.com/kadenzipfel/gas-optimizations/issues/15, the best way to avoid situations like this in the future would be to document the methodology that you used to obtain the reported gas costs, so that others can repeat your process and catch mistakes more easily.

PaulRBerg commented 1 year ago

Update: this conversation on Twitter is related to this issue.

As per transmission11's reply there, it does look like in an older version of Solidity, x != 0 used to be more gas efficient than x > 0.

Tagging @hrkrshnn in case he can illuminate us here?

hrkrshnn commented 1 year ago

I will follow up tomorrow.

PaulRBerg commented 1 year ago

Thanks @hrkrshnn, though no rush from my end.

hrkrshnn commented 1 year ago

@paulrberg The main difference is the use of via-ir. There are rules for transforming these expressions into an equivalent cheaper expression. But these rules are more effective in the new compilation pipeline. The legacy codegen and its optimizer works across basic blocks of assembly. And sometimes these expressions get split across basic blocks. See: https://hrkrshnn.com/t/devconnect.pdf#page=4 for another example.

I think these expressions should have the same cost if viaIR=true and the optimizer is enabled. Please don't hesitate to ping me again / open an issue in the original repo if that's not the case.

PaulRBerg commented 1 year ago

Thanks @hrkrshnn, makes sense.

What I don't understand though is that when I compiled the the code snippets above in Remix, I did not enable via-ir.

Is it that in the latest versions of Solidity, the compiler automatically applies via-ir to some operations, like != and >?

hrkrshnn commented 1 year ago

@paulrberg via-ir is not enabled by default yet. You can expect it to be default in an upcoming breaking release. The easiest way to test would be in Foundry, by adding viaIR = true in the toml config.

PaulRBerg commented 1 year ago

You can expect it to be default in an upcoming breaking release

Good to know!

via-ir is not enabled by default yet

This doesn't clear up the mystery yet of why the code snippets all yield the same gas cost even if --via-ir is not enabled.

hrkrshnn commented 1 year ago

This doesn't clear up the mystery yet of why the code snippets all yield the same gas cost even if --via-ir is not enabled.

It's likely because it uses different opcodes. See https://godbolt.org/z/q8K4WGqzr

PaulRBerg commented 1 year ago

Thank you ser, will take a look.