Storage vs Memory vs Calldata
Shlok Kumar
Posted on January 29, 2023
Storage:
Storage is the most straightforward concept to grasp because it is where all state variables are kept. Because the state of a contract can be changed (for example, within a function), storage variables must be mutable to be used. Their position, on the other hand, is permanent, and they are maintained on a blockchain system.
Whenever possible, several values of state variables will occupy the same storage slot to maximize storage efficiency. Aside from the exceptions such as dynamically sized arrays and structs, all other variables are compressed into blocks of 32 bytes.
If the total size of these variables is less than 32 bytes, they will be concatenated to occupy the same slot as the previous variable. If this is not the case, they will be moved to the next available storage slot. The data is stored consecutively (that is, one after the other), beginning with the 0 slots (and progressing to slots 1, 2, 3, and so on), in the sequence in which they were declared in the contract.
In all cases, dynamic arrays and structs take up a new storage slot, and any variables that come after them will be initialized to take up a new storage slot as well. Because the size of both dynamic arrays and structs is unknown a priori (i.e. until they are assigned later in your contract), they cannot be stored with their data in between other state variables as would otherwise be possible. Rather than taking up 32 bytes, they are assumed to take up 32 bytes, and the components contained within them are stored starting at a different storage slot that is computed using a Keccak-256 hash, as described above.
Constant state variables, on the other hand, are not saved into a storage slot. Instead, they are injected directly into the contract bytecode, and anytime those variables are read, the contract immediately switches them out for the constant value that was assigned to them in the beginning.
Memory:
Variables specified within the scope of a function are given priority in terms of memory allocation. They only exist for the duration of the function call, and as a result, they are transient variables that cannot be retrieved outside of the function call (i.e. anywhere else in your contract besides that function). They are, however, modifiable inside the scope of that function.
There are four 32-byte slots for memory in Solidity, each with a specific bytes-per-byte range, which is as follows:
64 bytes of scratch space for hashing methods;
32 bytes of currently allocated memory size, which is the free memory pointer where Solidity always places new objects
a 32-byte zero slot — which is used as the initial value for dynamic memory arrays and should never be written to.
It is because of these design variations that there are instances where arrays and structs will use varying amounts of space depending on whether they are in storage or memory.
uint8[4] arr;
struct Str {
uint v1;
uint v2;
uint8 v3;
uint8 v4;
}
In this example, each of the array arr and the struct Str takes up 128 bytes of memory in each of the two circumstances (i.e. 4 items, 32 bytes each). However, as storage, arr merely takes up 32 bytes (1 slot), whereas Str takes up 96 bytes (3 slots, 32 bytes each).
Calldata:
Calldata is an immutable, temporary storage place where function arguments are kept, and it operates largely like the memory in terms of performance and responsiveness. It is suggested to make use of calldata wherever possible because it avoids the creation of unneeded copies and assures that the data is not updated. Return values from functions can additionally include arrays and structs with the calldata data location.
ABI specifies a format for this sort of data, which is considered to be in multiples of 32 bytes and follows the ABI definition (which differs from internal function calls). The arguments for constructors are a little different, in that they are appended directly to the end of the contract's code instead of being prefixed with the contract's name (also in ABI encoding).
Comparisons:
Once a reference type variable (array or struct) is defined, the data location for that variable must also be specified unless the variable is of a type state, in which case it is automatically interpreted as storage. Since Solidity v0.6.9, memory and calldata are permitted in any functions, regardless of whether or not they are publicly visible (ie external, public, etc).
Similarly, to objects or arrays in JavaScript, assignments will either result in copies of the data being generated or simply references to the same piece of data being created:
Assignments between storage and memory (or from calldata) always result in the creation of a new copy.
Assignments from one memory to another only result in the creation of references. As a result, altering one memory variable affects all other memory variables that refer to the same data as the changed memory variable.
Assignments from storage to a local storage variable also only assign a reference to the storage variable in question.
Everything else that is assigned to storage is always copied.
It is advised to use calldata rather than memory when passing array parameters into functions because this results in significant gas savings. For example, using calldata in a summing function that loops over an input array can save about 1829 gas (or 3.5 percent) on average.
// Gas used: 50992
function func1 (uint[] memory nums) external {
for (uint i = 0; i < nums.length; ++i) {
...
}
}
// Gas used: 49163
function func2 (uint[] calldata nums) external {
for (uint i = 0; i < nums.length; ++i) {
...
}
}
For more content, follow me on - https://linktr.ee/shlokkumar2303
Posted on January 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
December 1, 2021