根据 JVM规范,有几条指令针对使用一组特定的常量进行了优化。谁能解释为什么只定义了这个常数范围?
- iconst_n:压入整数常量 n,0 ≤ n ≤ 5
- lconst_n:推长常量n,0≤n≤1
- fconst_n:推送浮点常量 n,0 ≤ n ≤ 2
- dconst_n:push double 常数 n,0 ≤ n ≤ 1
我认为这是由于使用这些常数的频率,但我找不到我的想法或任何其他有关它的信息的确认。
根据 JVM规范,有几条指令针对使用一组特定的常量进行了优化。谁能解释为什么只定义了这个常数范围?
我认为这是由于使用这些常数的频率,但我找不到我的想法或任何其他有关它的信息的确认。
意图已被明确提及,例如在JVMS, §3.2 中。常量、局部变量和控制结构的使用:
Java 虚拟机经常利用某些操作数的可能性(
int
常量-1、0、1、2、3、4和5在iconst_<i>指令的情况下)通过使这些操作数隐含在操作码中。因为iconst_0指令知道它将推送一个int
0
,所以 iconst_0不需要存储操作数来告诉它要推送什么值,也不需要获取或解码操作数。将0的 push 编译为bipush 0是正确的,但会使编译后的代码为spin
¹ 长一个字节。一个简单的虚拟机在每次循环中都会花费额外的时间来获取和解码显式操作数。隐式操作数的使用使编译后的代码更加紧凑和高效。
¹这是正在讨论的示例代码,即从零到一百的 for 循环
它不是这种类型的唯一优化,例如,有一些特殊指令用于访问堆栈帧中的局部变量的第一个。
这些操作在指令集中也有特殊的支持。在
spin
中,使用istore_1和iload_1指令将值传入和传出局部变量,每个指令都隐式地对局部变量1进行操作。
还要注意方便的指令iinc的存在,它是唯一直接对局部变量进行操作的指令。因此,计数循环,通常从零或一开始,并将计数器增加一个像 1 这样的小值,是这些优化指令的主要用例。
小变量索引的优化是合理的,因为这些索引按升序分配this
(如果不是static
),然后是参数,然后是第一个局部变量。原则上,编译器可以通过对变量重新排序以在优化索引处具有最常用的变量来进一步优化这一点,但实际上,这不会发生。
对于像 HotSpot 这样的优化 JVM,使用这些指令肯定没有性能优势,但它们仍然使字节码更短。
谁能解释为什么只定义了这个常数范围?
这在当时似乎是个好主意。字节码基于较旧的虚拟机实现,并且可能继承了这些约束。
我认为这是由于这些常数的使用频率,
我根据不同指令问世几年后使用不同指令的频率做了一些研究,发现几乎没有证据表明可以通过经验方法来确定哪些指令是 1 字节还是 2 字节。话又说回来,在编写原始设计时,生成的字节码非常少。