天下没有免费的合约,这是以太坊开发者的常识。既然EVM上处处要真金白银,那么能替雇主节约钱自然就成了优秀开发者的指标之一。这篇论文的出现恰好为广大开发者提供了指引。
总的来讲,作为开发者,我们需要牢记下面几个事实:
- 合约编译后的字节码大小会影响gas。
- 变量的位置和gas消耗的关系:stack < memory < storage。
- 区块链相关的操作超级昂贵,如创建合约。
- 每行代码的执行都要钱,能精简就精简。
费钱反模式
论文给出了7个浪费gas严重的反模式,作为开发者来讲,需引以为戒。这个7个模式被划分为两大类:
- 无用代码类
- 循环相关类
模式1:死代码
function p1 ( uint x ){
if ( x > 5)
if ( x*x < 20)
XXX
}
显然,若x>5,x*x<20将永远无法成立。其危害在于增加了合约的字节码大小。
模式2:模糊谓词(opaque predicate)
function p2 ( uint x ){
if ( x > 5)
if ( x > 1)
XXX
}
很明显,x>1是多余的,它的危害跟上面一样,增加了合约大小。
模式3:循环内昂贵的操作
uint sum = 0;
function p3 ( uint x ){
for ( uint i = 0 ; i < x ; i++)
sum += i;
}
此处的对于初学者就没有那么明显了。这里的sum是状态变量,存储于storage。由上可知,操作storage为昂贵操作,既然如此,换成下面的代码将节约不少gas:
uint sum = 0;
function p3 ( uint x ){
unit localSum = sum;
for ( uint i = 0 ; i < x ; i++)
localSum += i;
sum = localSum;
}
模式4:循环的输出是常数
function p4 () returns ( uint ){
uint sum = 0;
for ( uint i = 1 ; i <= 100 ; i++)
sum += i;
return sum;
}
在此,返回5050将避免昂贵的循环操作。
模式5:循环融合(loop fusion)
function p5 ( uint x ){
uint m = 0;
uint v = 0;
for ( uint i = 0 ; i < x ; i++)
m += i;
for ( uint j = 0 ; j < x ; j++)
v -= j;
}
显然,两个循环可以结合在一起,避免一次多余的循环。
模式6:循环内重复计算
uint x = 1;
uint y = 2;
function p6 ( uint k ){
uint sum = 0;
for ( uint i = 1 ; i <= k ; i++)
sum = sum + x + y;
}
这里的循环每次都重新计算了一次(x+y),更何况这两个还是状态变量,让整个gas的消耗更加雪上加霜。改进:
uint x = 1;
uint y = 2;
function p6 ( uint k ){
unit tmp = x + y;
uint sum = 0;
for ( uint i = 1 ; i <= k ; i++)
sum = sum + tmp;
}
模式7:循环内单向输出比较(Comparison with unilateral outcome in a loop)
function p7 ( uint x , uint y ) returns ( uint ){
for ( int i = 0 ; i < 100 ; i++)
if ( x > 0 ) y+=x;
return y;
}
这里的问题在于,循环内重复条件的比较,改成下面的代码将避免这种开销:
function p7 ( uint x , uint y ) returns ( uint ){
if ( x <= 0 ) return y;
for ( int i = 0 ; i < 100 ; i++)
y+=x;
return y;
}
总结
由于以太坊上跑代码花的是实实在在的Money,作为开发者,我们从来不曾如此的重要!归根结底一句话,开发的好日子来啦!
但是,这里有一个前提:写出既好又省钱的代码来。论文中给出的例子只是起点,帮助大家打开思路,至于其他就要靠各位自己总结和发现了。
网友评论