short -> int -> long 类型提升:有任何开销吗?

short -> int -> long type promotion: is there any overhead?

例如,如果我将方法的 return type/parameter 定义为 char,但调用者和实现实际上立即将其用作 int,是否存在任何开销?如果我理解正确的话,堆栈上的值无论如何都是 32 位对齐的,'registers' 也是如此(对不起,我不精通字节码)。

一句话解释:我正在编写用于解析和格式化二进制流的低级代码。我需要单个位的表示,在索引流以读取和更新单个位时使用。这是 Scala,我正在使用一个值 class,它是一个在编译时被擦除为选定的 java 原始类型的构造。这意味着方法定义为:

class Bit(val toInt :Int) extends AnyVal

@inline def +=(bit :Bit) = ...
@inline def +=(int :Int) = ...

在编译时相互冲突,因为它们在字节码中都是$plus$eq$(int)。 显然有很多解决方法,其中主要以不同的方式命名方法,但我宁愿避免使用它以防万一。 int 是位表示的自然选择,因为它是任何按位运算的结果,因此从 word >> offset & 1Bit 的 'conversion' 是空操作,并且同样,也可以将它们放在按位表达式中而不需要任何。如您所见,非常细粒度的东西。

我不会使用 boolean,因为在与 int 相互转换时似乎没有任何方法可以绕过条件表达式,但我考虑过 [=11] =],否则将不会被使用(即,不需要读写字符,因为它们比我在这个级别处理的抽象要高得多)。

那么,将 chars 投入到按位运算中是否始终会影响所有事情,或者它是否比方法调用快两个数量级(如创建和弹出的开销)激活记录)?

问题是你的问题基本上无法回答。

从字节码的角度来看,是的,有开销:你可以使用 javap -c 到 'disassemble' class 文件(显示字节码),你会观察到该类型的提升由实际的字节码处理。例如,这个:

class Test {
   void example() {
        int a = 0;
        long b = 0L;
        foo(a);
        foo(b);
    }

   void foo(long c) {}
}

然后 javap it...

它向您展示了当 int 被提升为 long 时涉及 I2L 操作码,而如果您直接使用 long,则此字节码不是 -少了一个字节码。

但是 - 您不能仅以这种方式将字节码推断为机器码。 class 文件(字节码)非常简单,完全未优化的结构,JVM 只需遵循 JVM 规范的规则,JVMS 通常不会指定时间和其他行为。

例如,在实践中,JVM 执行所有代码的速度非常慢,只是 'stupidly' 解释字节码,并浪费额外的时间和内存做一些基本的簿记工作,比如跟踪分支的路径(if) 倾向于去。

然后,如果热点注意到某些方法被调用得相当多,则需要一些时间,并使用该簿记来生成经过微调的机器代码。在 fallthrough case 比 jump case* 更快的 CPUs 上,它将使用 if 倾向于去的方向的簿记来优化,以便更常见的情况得到 fallthrough。它甚至会展开循环并进行各种惊人且影响深远的优化。毕竟这是1%的代码占用了99%的时间,所以花比较长的时间来产出优化后的机器码是值得的。

我什至不知道 I2L 本身(即使没有热点参与)是否会花费大量时间。这是一个可以完全在寄存器中完成的指令,它是一个单字节操作码,并且使用流水线 CPUs 按原样工作,我敢打赌在绝大多数情况下,这实际上花费了 0 额外时间,它是偷偷摸摸的在其他操作之间。涉及到热点,它很可能最终完全被优化掉了。

所以,问题就变成了,在你的目标硬件上,你有java的特定版本(从oracle的java8到OpenJ9 14,这里有很多选择,它是CPUs、操作系统和 JVM 版本的组合爆炸),'bad' 怎么样。

也许这是一个通用库,而您的目标是所有这些(许多版本、许多操作系统和 CPUs),没有简单的答案:使用像 JMH 这样的工具来彻底在许多平台上测试性能 - 或者假设开销可能对某些奇特的组合很重要。

但是如果你可以限制 JVM 和 arch/OS 很多,那么这会变得容易得多 - 只需 JMH 你的目标部署,现在你知道了。

不管它的价值如何,我敢打赌这次促销最终的成本不足以在这里发挥作用(更不用说,根本不会出现在 JMH 中)。

*) 在绝大多数 CPU 上,唯一可用的分支指令是 'GOTO this place in the code IF some flag is set' - 所以要写一个 if,你首先要写 GOTO a bunch ahead if condition,然后 else代码,以GOTO the line after the if block结尾,然后是if代码。

注意:您可以在启动 java 可执行文件时使用一些 -XX 参数,让它在热点某个方法时打印出来,甚至要求它打印机器码生成的,然后你可以通过反汇编程序查看真正重要的代码:在你的 CPU 上实际结束的是 运行。由于 CPU 流水线,即使有一条额外的指令也不会花费太多。

NB2:在 32 位架构上,long 型一般比 int 型成本高很多,但现在 32 位架构很少见,所以我怀疑这是否重要。