Vyper: Viper num types can waste lots of money

Created on 21 Dec 2017  路  14Comments  路  Source: vyperlang/vyper

We are developing an application using Viper, and it is important for us to minimize
gas storage costs.

It seems that currently viper can do only 128 and 256 bit stored values (num128 and num256).
Some of our variables use much less than 128 bits, we want to store only what we need.

We can store our variables as byte arrays, but then we will need to do a conversion between a byte array and a num each time we store or load a variable.

Do you guys plan shorter types anytime soon? I think the compiler could automatically fit many short variables into one num.

Or do you expect us doing optimizing things manually each time?

Discussion

All 14 comments

While Solidity does indeed offer this feature, if you read the documentation you'll notice this only has a benefit when dealing with arrays of shorter values, as the EVM is actually optimized for reading on 32 byte (256bit) boundaries.

Additionally, this feature is typically very error prone in most compilers that implement it (C is a great example) and only has a very limited utility on processors that operate on types less than their canonical size (see 32-bit/64-bit tradeoffs, etc.)

For Viper, we haven't started relentlessly optimizing the resulting bytecode yet, and while this is definitely one strategy for doing so, for the reasons noted it's not often a highly rewarding path, so I doubt you will see this feature for quite some time.

That being said, typically there are orders of magnitude that can be gained simply from optimizing your program itself towards how the underlying processor operates (which is a 256-bit simple stack-based processor). I believe the gains from optimizing your own algorithms will be a much more effective means of reducing your gas usages, rather than doing custom things with bytearrays and such. If your gas usages are high enough as to pursue serious reductions, then there are usually many different ways of reducing your computation that would be more effective than this proposal. Code optimization is definitely an art, and one that is particularly important for the EVM due to the costs involved.

If you had a snippet of code to share that you believe to be particularly gas-intensive, we could look into the optimality of the Bytecode it produces, or suggest alternative methods for obtaining what you desire.

Bryant - thank you !

Since default types for Viper are 128 bit, I suspect that you guys are fitting two of them in one EVM slot - can you please confirm that? The documentation does not provide any info on this subject.

For our network, storage costs are hugely important, so if we stay with viper and do not go back to solidity, we will essentially need to write a macro library to constantly translate from byte arrays to numbers.

In any case, with ETH costs rising, IMHO you guys will need to implement a slot-packing functionality similar to Solidity, otherwise you will be pushing people back to Solidity.

Well, can you provide some evidence that Solidity is even doing this? From my brief review of their documentation I think they only tightly pack structs and maybe arrays, not standalone variables. Are structs and arrays the issue, or are you more generally talking about tightly packing everything?

Bryant - I think with gas prices skyrocketing tightly packing EVERYTHING becomes ISSUE NUMBER ONE.

Would you agree?

From my understanding of the documentation, tightly packing regular variables would actually raise the overall gas costs as the EVM is optimized to load/store in 32-byte chunks. Only for larger accesses (e.g. structs/arrays) is there a benefit to tightly packing. IIRC what I learned in my computer architecture course, this is typical to most language runtimes including C and Java and most commercial CPUs.

That's why I wanted to know if you had any further proof or documentation to point to the fact that Solidity does this, because with my limited knowledge I didn't think this was done.

Bryant - I am not sure you are correct when you are saying that tightly packing regular variables would actually rise the costs. I think the gas costs are only calculated for the delta between the original state and the final state. Can you give an example?

From the solidity documentation:
"WARNING: When using elements that are smaller than 32 bytes, your contract鈥檚 gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size."

The argument is a bit nuanced. It will reduce size in storage, but every access to that storage location has to use special processing in order to access it, increasing gas costs for the access. Since in EVM gas is charged per access as well as per storage, I think it is not clear that the cost associated with special access outweighs the benefits of reduced storage volume unless you are dealing with structs or arrays that store multiple elements. In those cases (structs/arrays) there is definitely a clear benefit to optimizing types.

Realistically, the best way to figure this out is to profile gas usages for different accesses across multiple types with multiple read/write ratios. If you are willing to do that, be my guest. Would be interesting to have concrete evidence vs using common wisdom.

Interesting thread. But clearly one person didn't even try to understand what the other was saying ^^

Well, I'll accept that criticism since I never answered OP's question 馃榿

TL;DR: Vyper has no near-term plans to implement smaller types, or optimize storage of types to reduce gas costs (aka tightly-packing). As OP has noted, this should be considered for future roadmap.

You shouldn't though :)

A bit late to this conversation but a simple way to confirm that Solidity does not optimize for storage in uin8 or other smaller width numbers would've been to do this:

pragma solidity ^0.4.24;

contract Set {
    uint256 public num256;
    uint8 public num8;

    function set_uint8(uint8 _i) public {
        num8 = _i;
    }

    function set_uint256(uint256 _i) public {
        num256 = _i;
    }
}

I tried this on Ropsten and the results are as follows:

  • set_uint256 needs 41647 gas to execute.
  • set_uint8 needs 41851 gas to execute.

So yeah, Solidity isn't optimizing storage for single variables and using uint256 is cheaper, gas wise.

Closing, we can take a look at this if it really becomes a big problem. Since it's really a big change and introduction of complexity and a potentially serious security risk - I am opting to close this for now.

Was this page helpful?
0 / 5 - 0 ratings