当前位置: 首页 > 图灵资讯 > 技术篇> 尽可能使用堆栈变量

尽可能使用堆栈变量

来源:图灵教育
时间:2024-02-25 14:01:38

如果您经常访问变量,您需要考虑从哪里访问这些变量。变量是 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 执行。