Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
PaulRBerg opened this issue Nov 28, 2022 · 9 comments
Open

Comments

@PaulRBerg
Copy link

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
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.

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 #3 and #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
Copy link
Author

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
Copy link

I will follow up tomorrow.

@PaulRBerg
Copy link
Author

Thanks @hrkrshnn, though no rush from my end.

@hrkrshnn
Copy link

@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
Copy link
Author

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
Copy link

hrkrshnn commented Dec 1, 2022

@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
Copy link
Author

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
Copy link

hrkrshnn commented Dec 1, 2022

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
Copy link
Author

Thank you ser, will take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants