In Ethereum, transactions cost gas and hence ether. The gas consumption of a transaction depends on the opcodes that the EVM has to execute. The gas cost for each Opcode can be found as explained in this question. Few common opcodes and gas are,
Operation Gas Description
ADD/SUB 3 Arithmetic operation
MUL/DIV 5 Arithmetic operation
ADDMOD/MULMOD 8 Arithmetic operation
AND/OR/XOR 3 Bitwise logic operation
LT/GT/SLT/SGT/EQ 3 Comparison operation
POP 2 Stack operation
PUSH/DUP/SWAP 3 Stack operation
MLOAD/MSTORE 3 Memory operation
JUMP 8 Unconditional jump
JUMPI 10 Conditional jump
SLOAD 200 Storage operation
SSTORE 5,000/20,000 Storage operation
BALANCE 400 Get balance of an account
CREATE 32,000 Create a new account using CREATE
CALL 25,000 Create a new account using CALL
This is a concern when it comes to smart contracts as transactions are also involved and it's important to consider the gas cost when designing a contract.
Reducing the gas consumed by a contract is important in two situations,
- Cost of deploying a contract
- Cost to call the contract functions
Cost of deploying a contract
For this, most of the optimizations are done at compilation time as described in the documentation-faqs,
Are comments included with deployed contracts and do they increase
deployment gas?
No, everything that is not needed for execution is
removed during compilation. This includes, among others, comments,
variable names and type names.
And the details of the optimizer can be found here.
Another way of reducing the size is by removing useless code. For example:
1 function p1 ( uint x ){
2 if ( x > 5)
3 if ( x*x < 20)
4 XXX }
In above code, line 3 and 4 will never be executed and these type of useless code can be avoided by carefully going through the contract logic and that will reduce the size of the smart contract.
Cost to call the contract functions
When contracts' functions are called, for the execution of function it costs gas. Hence optimizing functions to use less gas is important. There can be many different ways of doing it when individual contract is considered. Here are few that might save gas during execution,
- Reduce Expensive operations
Expensive operations are the opcodes that has more gas values such as SSTORE
. Below are some methods of reducing expensive operations.
A) Use of Short Circuiting rules
The operators || and && apply the common short-circuiting rules. This
means that in the expression f(x) || g(y), if f(x) evaluates to true,
g(y) will not be evaluated even if it may have side-effects.
So if a logical operation includes an expensive operation and a low cost operation, arranging in a way that the expensive operation can be short circuited will reduce gas at some executions.
If f(x) is low cost and g(y) is expensive, arranging logical operations
- OR :
f(x) || g(y)
- AND :
f(x) && g(y)
will save more gas if short circuited.
If f(x)
has a considerably higher probability of returning false compared to g(y)
, arranging AND operations as f(x) && g(y)
might cause to save more gas in execution by short circuiting.
If f(x)
has a considerably higher probability of returning true compared to g(y)
, arranging OR operations as f(x) || g(y)
might cause to save more gas in execution by short circuiting.
B) expensive operations in a loop
eg:
uint sum = 0;
function p3 ( uint x ){
for ( uint i = 0 ; i < x ; i++)
sum += i; }
In the above code, since sum
storage variable is read and written every time inside the loop, storage operations that are expensive take place at every iteration. This can be avoided by introducing a local variable as follows to save gas.
uint sum = 0;
function p3 ( uint x ){
uint temp = 0;
for ( uint i = 0 ; i < x ; i++)
temp += i; }
sum += temp;
- Other loop related patterns
loop combining
function p5 ( uint x ){
uint m = 0;
uint v = 0;
for ( uint i = 0 ; i < x ; i++) //loop-1
m += i;
for ( uint j = 0 ; j < x ; j++) //loop-2
v -= j; }
loop-1 and loop-2 can be combined and gas can be saved,
function p5 ( uint x ){
uint m = 0;
uint v = 0;
for ( uint i = 0 ; i < x ; i++) //loop-1
m += i;
v -= j; }
and a few more loop patterns can be found here.
- Using of Fixed-size bytes arrays
From Docs,
It is possible to use an array of bytes as byte[], but it is wasting a
lot of space, 31 bytes every element, to be exact, when passing in
calls. It is better to use bytes.
and
As a rule of thumb, use bytes for arbitrary-length raw byte data and
string for arbitrary-length string (UTF-8) data. If you can limit the
length to a certain number of bytes, always use one of bytes1 to
bytes32 because they are much cheaper.
having a fixed length always saves gas. Refer to this question as well.
Removing useless code as explained earlier under contract deployment will save gas even when functions are executed, if that can be done inside functions.
Not using libraries when implementing the functionality is cheaper for simple usages.
Calling library for simple usages may be costly. If the functionality is simple and feasible to implement inside the contract as it avoids the step of calling the library. execution cost for the functionality only will still be the same for both.
Using visibility external
for the functions only accessed externally forces to use calldata
as the parameter location and this saves some gas when the function executes.
Using memory
variables within functions locally when possible saves gas of accessing the storage
.
These are some ways of saving gas and there may be many other methods depending on the requirements.