如果您频繁存取变量,就需要考虑从何处存取这些变量。变量是 static 变量,还是堆栈变量,或者是类的实例变量?变量的存储位置对存取它的代码的性能有明显的影响?例如,请考虑下面这段代码:
class StackVars
{
private int instVar;
private static int staticVar;
//存取堆栈变量
void stackAccess(int val)
{
int j=0;
for (int i=0; i<val; i++)
j += 1;
}
//存取类的实例变量
void instanceAccess(int val)
{
for (int i=0; i<val; i++)
instVar += 1;
}
//存取类的 static 变量
void staticAccess(int val)
{
for (int i=0; i<val; i++)
staticVar += 1;
}
}
|
这段代码中的每个方法都执行相同的循环,并反复相同的次数。唯一的不同是每个循环使一个不同类型的变量递增。方法 stackAccess 使一个局部堆栈变量递增,instanceAccess 使类的一个实例变量递增,而 staticAccess 使类的一个 static 变量递增。
instanceAccess 和 staticAccess 的执行时间基本相同。但是,stackAccess 要快两到三倍。存取堆栈变量如此快是因为,JVM 存取堆栈变量比它存取 static 变量或类的实例变量执行的操作少。请看一下为这三个方法生成的字节码:
Method void stackAccess(int)
0 iconst_0 //将 0 压入堆栈。
1 istore_2 //弹出 0 并将它存储在局部分变量表中索引为 2 的位置 (j)。
2 iconst_0 //压入 0。
3 istore_3 //弹出 0 并将它存储在局部变量表中索引为 3 的位置 (i)。
4 goto 13 //跳至位置 13。
7 iinc 2 1 //将存储在索引 2 处的 j 加 1。
10 iinc 3 1 //将存储在索引 3 处的 i 加 1。
13 iload_3 //压入索引 3 处的值 (i)。
14 iload_1 //压入索引 1 处的值 (val)。
15 if_icmplt 7 //弹出 i 和 val。如果 i 小于 val,则跳至位置 7。
18 return //返回调用方法。
Method void instanceAccess(int)
0 iconst_0 //将 0 压入堆栈。
1 istore_2 //弹出 0 并将它存储在局部变量表中索引为 2 的位置 (i)。
2 goto 18 //跳至位置 18。
5 aload_0 //压入索引 0 (this)。
6 dup //复制堆栈顶的值并将它压入。
7 getfield #19 <Field int instVar>
//弹出 this 对象引用并压入 instVar 的值。
10 iconst_1 //压入 1。
11 iadd //弹出栈顶的两个值,并压入它们的和。
12 putfield #19 <Field int instVar>
//弹出栈顶的两个值并将和存储在 instVar 中。
15 iinc 2 1 //将存储在索引 2 处的 i 加 1。
18 iload_2 //压入索引 2 处的值 (i)。
19 iload_1 //压入索引 1 处的值 (val)。
20 if_icmplt 5 //弹出 i 和 val。如果 i 小于 val,则跳至位置 5。
23 return //返回调用方法。
Method void staticAccess(int)
0 iconst_0 //将 0 压入堆栈。
1 istore_2 //弹出 0 并将它存储在局部变量表中索引为 2 的位置 (i)。
2 goto 16 //跳至位置 16。
5 getstatic #25 <Field int staticVar>
//将常数存储池中 staticVar 的值压入堆栈。
8 iconst_1 //压入 1。
9 iadd //弹出栈顶的两个值,并压入它们的和。
10 putstatic #25 <Field int staticVar>
//弹出和的值并将它存储在 staticVar 中。
13 iinc 2 1 //将存储在索引 2 处的 i 加 1。
16 iload_2 //压入索引 2 处的值 (i)。
17 iload_1 //压入索引 1 处的值 (val)。
18 if_icmplt 5 //弹出 i 和 val。如果 i 小于 val,则跳至位置 5。
21 return //返回调用方法。
|
查看字节码揭示了堆栈变量效率更高的原因。JVM 是一种基于堆栈的虚拟机,因此优化了对堆栈数据的存取和处理。所有局部变量都存储在一个局部变量表中,在 Java 操作数堆栈中进行处理,并可被高效地存取。存取 static 变量和实例变量成本更高,因为 JVM 必须使用代价更高的操作码,并从常数存储池中存取它们。(常数存储池保存一个类型所使用的所有类型、字段和方法的符号引用。)
通常,在第一次从常数存储池中访问 static 变量或实例变量以后,JVM 将动态更改字节码以使用效率更高的操作码。尽管有这种优化,堆栈变量的存取仍然更快。
考虑到这些事实,就可以重新构建前面的代码,以便通过存取堆栈变量而不是实例变量或 static 变量使操作更高效。请考虑修改后的代码:
class StackVars
{
//与前面相同...
void instanceAccess(int val)
{
int j = instVar;
for (int i=0; i<val; i++)
j += 1;
instVar = j;
}
void staticAccess(int val)
{
int j = staticVar;
for (int i=0; i<val; i++)
j += 1;
staticVar = j;
}
}
|
方法 instanceAccess 和 staticAccess 被修改为将它们的实例变量或 static 变量复制到局部堆栈变量中。当变量的处理完成以后,其值又被复制回实例变量或 static 变量中。这种简单的更改明显提高了 instanceAccess 和 staticAccess 的性能。这三个方法的执行时间现在基本相同,instanceAccess 和 staticAccess 的执行速度只比 stackAccess 的执行速度慢大约 4%。
这并不表示您应该避免使用 static 变量或实例变量。您应该使用对您的设计有意义的存储机制。例如,如果您在一个循环中存取 static 变量或实例变量,则您可以临时将它们存储在一个局部堆栈变量中,这样就可以明显地提高代码的性能。这将提供最高效的字节码指令序列供 JVM 执行。
分享到:
相关推荐
这是一个基于任务的抢先式实时操作系统,旨在消耗尽可能少的资源,同时提高使用 AVR 微控制器的系统的有效利用率。 这个项目的动机源于我自己写这样的东西的愿望。还有许多其他可用的选项,它们可能写得更好,更...
安装该脚本没有依赖性,应在加载顺序中尽可能高地包含该脚本,以捕获可能发生的任何错误。配置日志级别需要从您的配置文件中指定。 变量可以在文件顶部找到。客户端日志级别这将确定错误和控制台。*消息是否显示在...
minidump可以定制,给我们带来了一个问题-保存多少应用程序状态信息才能既保证调试有效,又能够尽量保证minidump文件尽可能小?尽管调试简单的异常访问只需要调用堆栈和局部变量的信息,但是解决更复杂的问题需要更...
但是C/C++并没有约定全局变量之间的初始化顺序,如果其它全局变量的构造函数中有堆内存分配,则可能无法检测到。Visual Leak Detector使用了C/C++提供的#pragma init_seg来在某种程度上减少其它全局变量在其之前初始...
它被设计为使用起来尽可能简单。为什么是这个图书馆? 您可能想知道为什么我选择创建自己的库,而又有这么多其他库可用。 好吧,他们没有一个100%满意我。 有些只是基本的包装器(具有直接操作堆栈的函数),有些...
与忍者的区别武士试图尽可能匹配忍者的行为,但是在某些情况下,武士略有不同: samurai使用ninja手册中记录的,而ninja有一个怪癖( ),如果构建边缘没有变量绑定,则在文件级中在规则级变量之前查找变量。...
ZKVM介绍我们想要构建一个ZKP,以验证整个EVM块或尽可能多地验证它。 可以调整每个EVM操作码的用气成本。 如果它们非常昂贵,目前还可以排除一些操作码。 可以排除预编译。 此回购创建了一个草图,以此作为解释的...
根据开发需求,整合一些常用的嵌入式构件,以节约开发时间,尽最大可能地减少开发工作量;另外,要求这个实时操作系统能非常容易地嵌入到小容量的芯片中。毕竟,大系统是少数的,而小应用是多数而广泛的。显而易见,...
4.5.2 尽可能快地释放空闲资源 4.5.3 静态对象 4.5.4 庞大的对象 4.6 效率的权衡 4.6.1 实现更加困难 4.6.2 使用更加困难 4.7 总结 4.8 练习 4.9 参考文献和相关资料 第5章 错误 5.1 可重用代码中的错误...
特征该扩展程序具有许多功能,可以使您的Godot编程体验尽可能舒适: GDScript( .gd )语言的语法突出显示.tscn和.tres场景格式的语法突出显示全类型GDScript支持可选的“智能模式”,可通过动态键入的脚本提高生产...
尽可能用sizeof(varname)代替sizeof(type)。 使用sizeof(varname)是因为当变量类型改变时代码自动同步,有些情况下sizeof(type)或许有意义,还是要尽量避免,如果变量类型改变的话不能同步。 8. TODO注释(TODO ...
此外,尽可能避免正则表达式技巧,尽管它很重要,一旦您成功通过面试,我们鼓励您拿起它。 这里的重点是教学法而不是表演。 我一直努力使用相同的模板编写相关问题的解决方案和单个问题的替代解决方案,即变量/函数...
7.4.2. 使你的数据尽可能小 7.4.3. 列索引 7.4.4. 多列索引 7.4.5. MySQL如何使用索引 7.4.6. MyISAM键高速缓冲 7.4.7. MyISAM索引统计集合 7.4.8. MySQL如何计算打开的表 7.4.9. MySQL如何打开和关闭表 7.4.10. 在...
10.4.1 调用堆栈 549 10.4.2 单步执行到出错位置 551 10.5 测试扩展的类 555 10.6 调试动态内存 558 10.6.1 检查自由存储器的函数 558 10.6.2 控制自由存储器的调试操作 559 10.6.3 自由存储器的调试...
比例key_reads/key_read_requests应该尽可能的低, # 至少是1:100,1:1000更好(上述状态值可以使用SHOW STATUS LIKE 'key_read%'获得)。注意:该参数值设置的过大反而会是服务器整体效率降低 ft_min_word_len = 4 # ...
他们是: 会场日期接触支付演出长度(以小时为单位) 行驶距离(以英里为单位) 行程时间(小时) 每加仑汽油的成本每加仑载具英里数额外费用我想使用户体验尽可能简单,所以我只将相关信息放入图表中。 这些数据点...
” (这就是我们希望人们说的话...)哲学合理的默认值简单的根据您的需要/需要输出一点或尽可能多! 不要强迫人们*记住参数/参数的顺序。 允许的任意数量的参数。 聪明地环境变量作为设置__line(全局帮助你调试) ...
相反,我们使代码以自然的方式表达意图,并尽可能保存注释的使用。7、多环境配置ActFramework支持的概念profile使您可以轻松地在不同的环境(由配置文件定义)中组织配置。8、简单而强大的数据库支持内置多数据库...
答: 编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。 14、 static有...