3

Say I have an uint16 _16bits with binary value 1110_1011_0001_0001, what would be the most gas efficient operation to get the value of any particular index in the range of [0, 15]?

For clarity:

using LowGasOperator for uint16;

 _16bits.getValue(0) == 1
 _16bits.getValue(1) == 0

What would be the most gas efficient definition of LowGasOperator.getValue?

2 Answers 2

4
+50

I did some test with solc: 0.8.10+commit.fc410830.Emscripten.clang and the optimizer enabled (200 runs), and I came up with a function that is 45% less expensive than the double shift one you proposed:

// Gas cost: 103
function getValue(uint four_nibbles, uint mask) public pure returns (uint) {
    return (four_nibbles & mask) > 0 ? 1 : 0;
}

To squeeze a lot more you can do this:

// Gas cost: 27
function getValue(uint four_nibbles, uint mask) public pure returns (bool) {
    return (four_nibbles & mask) > 0;
}

But it depends on your use case if you can adapt your business logic accordingly.

These functions use a bitwise AND operator (&) with a mask instead of an index, so for example to get the 1st bit you pass 1, the 2nd bit you pass 2, the 3rd bit you pass 4, the 4th bit you pass 8, and so on.

Please note that using unit16 and in general, any uint smaller than 256 bits, can easily increase the cost of the overall operations. The EVM always works with variables made of 256 bits, so when you use different sizes you introduce a surplus of work, and the gain from using smaller numbers rarely compensates for the added work. Check always on the field if the cost is really reduced.

For example in your specific case, the double shift function costs 188 gas with uint16 and it costs 142 gas with uint (that is an alias for uint256):

// Gas cost: 142
function getValue(uint four_nibbles, uint index) public pure returns (uint) {
    return (four_nibbles << (255 - index)) >> 255;
}

// Gas cost: 188
function getValue(uint16 four_nibbles, uint16 index) public pure returns (uint16) {  
    return (four_nibbles << (15 - index)) >> 15;
}

I created a dedicated repo for you if you want to test it by yourself. Please don't take it as an example of good programming, it is just a very raw and fast way to test gas costs ;)

4
  • How does using modifier view instead of pure effects the gas price? Example: function getValue(uint16 four_nibbles, uint16 index) public view returns (uint16) Could you test it quick?
    – Sky
    Jan 10 at 10:08
  • 1
    "View" it's not needed if you don't access storage variables. In your case, you don't use storage so "pure" is the correct and cheapest way to do it. If you try to use "view", Solidity compiler is smart enough to avoid it anyway in the compiled bytecode, so the costs remain actually the same. I don't suggest to do it anyway, because using view you are almost "lying" to the developer that read the source code and cannot read the assembly. In the best case the reader see that view modifier as an error, at worst he thinks your method will access the storage in some way he don't understand. Jan 10 at 23:46
  • I created a repo for you if you want to test more by yourself: github.com/InternetOfPeers/test-gas-costs Jan 10 at 23:54
  • Thanks, I will try to read more on it to better get the point.
    – Sky
    Jan 11 at 7:52
0

I'm not sure why I didn't think of this earlier, but the function below does exactly what I want(though I'm not sure if it's the most gas efficient way of doing it). So I basically shake off all the bits to the left of the desired bit by shifting the desired bit to the 15-bit position(0-indexed), then I shake off all the bits to the right of the desired bit by shifting the desired bit to the 0-bit position.

function getValue(uint16 four_nibbles, uint16 index) public pure returns(uint16) {
    return (four_nibbles << (15 - index)) >> 15;
}     

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.