【问题标题】:Are some Java bytecode instructions unnecessarily typed?是否输入了一些不必要的 Java 字节码指令?
【发布时间】:2020-10-27 00:33:34
【问题描述】:

https://docs.oracle.com/javase/specs/jvms/se14/html/jvms-2.html#jvms-2.11.1 中所述,将操作数类型编码为操作码是有代价的:

鉴于 Java 虚拟机的一字节操作码大小,将类型编码为操作码会对其指令集的设计造成压力。如果每条类型化指令都支持 Java 虚拟机的所有运行时数据类型,那么指令的数量将超过一个字节所能表示的数量。

因此,似乎只应该对需要操作数类型信息或启用优化的指令执行此操作。例如,需要区分iaddfadd,因为整数和浮点数的加法实现方式不同。而且我不知道为什么从数组(分别为baloadiaload)加载booleanint 有不同的指令,但我至少可以想象一些性能原因。

但是,为什么将int (istore) 和float (fstore) 存储到局部变量中的指令不同?它们不应该以完全相同的方式实现吗?

这个答案https://stackoverflow.com/a/2638143 说字节码验证器需要输入指令。但这真的有必要吗?在方法中,所有数据都从方法的参数(类型已知)和类字段(类型也已知)流向其他类字段和返回值。因此,由于输入和输出的类型是已知的,我们不能为指令重建任何缺失的类型吗?事实上,字节码验证器不就是这样做的吗,因为它必须检查类型,即它必须知道哪些类型是预期的?

简而言之:如果我们将istorefstore 组合成一条指令,会发生什么问题?性能或便携性会受到影响吗?字节码验证会停止工作吗?

【问题讨论】:

    标签: java jvm bytecode


    【解决方案1】:

    istorefstore 在几乎每个 JVM 和我曾经使用过的每个架构上都以不同的方式实现。

    例如,在HotSpot JVM x64解释器中,istore_0被实现为

    mov dword ptr [r14], eax
    

    fstore_0 被实现为

    movss dword ptr [r14], xmm0
    

    解释器将栈顶值缓存在一个寄存器中,整数和浮点值有不同的寄存器。

    同样,baloadiaload 的实现方式不同,因为它们使用不同的偏移乘数(分别为 1 和 4),并且需要不同的机器指令来加载 8 位值和 32 位值。

    正如您所注意到的,一些类型信息可以从数据流分析中得出,字节码验证器无论如何都会这样做。但是为了在运行时使用这些信息,堆栈槽和局部变量需要以某种方式标记为相应的类型,并且合并的字节码指令需要在运行时读取这个标签并根据标签进行调度。当然,这在运行时性能和已用内存方面都不是最理想的。

    【讨论】:

    • 您不需要标记数据槽。可以通过将字节码转换为具有输入指令的 IR 来重新获取信息。但这将排除最简单的解释器以及大多数实现所具有的验证器和实际执行环境的分离。
    【解决方案2】:

    我认为您对无需输入的加载和存储是正确的。我自己不是 Java 的原始设计者之一,我只能推测为什么会这样设计。但这是我的猜测。

    我认为,在最初设计 Java 时,它的设计考虑了解释性,并且他们可能认为对于 int 和 float 需要以不同的方式实现加载和存储。他们也可能希望无需验证即可运行(事实上,今天仍然可以禁用字节码验证)。最后,虽然在加载和存储指令无类型时仍然可以进行相同的字节码验证在技术上是可行的,但这会使事情变得稍微复杂一些。

    验证可以通过无类型加载和存储来完成的一个简单证明是,局部变量表的行为类似于操作数堆栈,并且存在对操作数堆栈进行操作的无类型指令(dupswap 等.).

    【讨论】:

    • 大多数实现都有独立于实际解释器/编译器实现的验证器。因此,无论是否支持关闭验证器,它都不会在这些实现中向解释器提供元信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-13
    • 1970-01-01
    • 2020-02-11
    • 2014-02-07
    • 2014-05-05
    • 1970-01-01
    相关资源
    最近更新 更多