Java长循环中冗余赋值与赋值前检查的性能开销对比
长循环场景下冗余赋值与条件判断的资源开销对比
基础场景:局部变量操作
注:第二个示例循环次数写为10_000应为笔误,以下按二者均循环10万次、else分支命中5万次的前提对比:
实现1:else分支直接赋值
int count = 0; for(int i=0; i<100_000; i++){ if (...) { count++; doLogic(count); // logic is strictly related with count } else { count = 0; //50000次冗余赋值 } }
实现2:赋值前先做条件判断
int count = 0; for(int i=0; i<100_000; i++){ if (...) { count++; doLogic(count); // logic is strictly related with count } else { if(count > 0) { // 50000次条件检查 count = 0; } } }
开销对比结论
局部变量场景下,实现1(直接冗余赋值)的开销更低:
- 基础类型
int的局部变量存储在栈空间或者CPU寄存器上,赋值操作是单周期CPU指令,哪怕冗余执行,开销几乎可以忽略 - 实现2每次else分支都要额外执行「读取count值 + 数值比较」两个操作,哪怕CPU分支预测命中率100%,也比直接赋值多了运算步骤;如果count的变化规律不规则导致分支预测失败,还会触发CPU流水线冲刷,额外开销会进一步拉大
- 内存层面二者没有差异,都只占用固定的栈空间存储count变量,没有额外内存占用
延伸场景:count封装在Spring单例对象中
这种场景下操作的不再是栈上的局部变量,而是堆内存中单例对象的成员变量,同时多了方法调用的开销,二者的开销差异会发生变化,没有绝对的优劣,要看业务场景中count>0在else分支中的出现概率:
- 如果
count>0的概率很低(比如100次else分支里只有1次需要重置):实现2更划算,少量的getCount()读操作+比较的开销,远低于大量冗余的resetCount()写操作开销 - 如果
count>0的概率很高(比如超过30%的else分支都需要重置):实现1更划算,省去了每次getCount()的方法调用、堆内存读取、比较的开销,直接调用resetCount()反而总开销更低
额外注意如果count加了volatile修饰保证多线程可见性,堆读写的开销会进一步提升,更建议根据命中概率选择方案。
内容的提问来源于stack exchange,提问作者Ermal
相关产品推荐
相关产品推荐

