Java 短->;int->;长型促销:有开销吗?
例如,如果我将方法的返回类型/参数定义为Java 短->;int->;长型促销:有开销吗?,java,performance,jvm-bytecode,Java,Performance,Jvm Bytecode,例如,如果我将方法的返回类型/参数定义为char,但调用者和实现实际上立即将其用作int,是否有任何开销?如果我理解正确,堆栈上的值无论如何都是32位对齐的,“寄存器”也是如此(对不起,我不太熟悉字节码) 在您进行否决表决或在阅读之前关闭之前,请给出一个解释:我正在编写用于解析和格式化二进制流的低级代码。我需要一个单个位的表示,用于索引流以读取和更新单个位。这是Scala,我使用的是一个值类,这是一个在编译时被删除为所选java原语类型的构造。这意味着方法定义为: class Bit(val t
char
,但调用者和实现实际上立即将其用作int
,是否有任何开销?如果我理解正确,堆栈上的值无论如何都是32位对齐的,“寄存器”也是如此(对不起,我不太熟悉字节码)
在您进行否决表决或在阅读之前关闭之前,请给出一个解释:我正在编写用于解析和格式化二进制流的低级代码。我需要一个单个位的表示,用于索引流以读取和更新单个位。这是Scala,我使用的是一个值类,这是一个在编译时被删除为所选java原语类型的构造。这意味着方法定义为:
class Bit(val toInt :Int) extends AnyVal
@inline def +=(bit :Bit) = ...
@inline def +=(int :Int) = ...
编译时相互冲突,因为它们在字节码中都是$加$eq$(int)
。
很明显,有很多方法可以解决这个问题,其中的主要方法是以不同的方式命名方法,但我宁愿避免使用它,以防它不重要。int
是位表示的自然选择,因为它是任何按位操作的结果,因此从word>>offset&1
到位的“转换”是不可操作的,同样,它们也可以在不需要任何操作的情况下放入按位表达式中。正如你所看到的,非常细粒度的东西
我不会使用boolean
,因为在转换为int
或从int
转换时,似乎没有任何方法可以绕过条件表达式,但我考虑了char
,否则它将不被使用(也就是说,不需要读和写一个字符,因为它们是比我在这个级别处理的抽象概念高得多的抽象概念)
那么,将char
s放入按位操作是否会影响任何事情,或者它比方法调用快两个数量级(如创建和弹出激活记录的开销)?问题是,您的问题本质上是无法回答的
从字节码的角度来看,是的,这是有开销的:您可以使用javap-c
来“反汇编”类文件(显示字节码),并且您会发现类型升级是由实际的字节码处理的。例如:
class Test {
void example() {
int a = 0;
long b = 0L;
foo(a);
foo(b);
}
void foo(long c) {}
}
然后
它向您展示了当int
提升为long
时,会涉及到I2L
操作码,而如果您直接使用long,则该字节码不会-它会短一个字节码
然而您不能以这种方式将字节码外推到机器码中。类文件(字节码)是非常简单、完全未优化的结构,JVM只需遵循JVM规范的规则,JVM通常不指定计时和其他行为
例如,在实践中,JVM执行所有代码的速度非常慢,只是“愚蠢地”解释字节码,并浪费额外的时间和内存做一些基本的簿记,比如跟踪分支(如果)的走向
然后,如果hotspot注意到某个方法被大量调用,它将需要一些时间,并使用该簿记来生成经过微调的机器代码。在故障案例比跳转案例快*的CPU上,它将使用该簿记来优化if
的走向,以使更常见的案例得到故障ough。它甚至会展开循环,并进行各种令人惊讶和深远的优化。毕竟,这是代码的1%,需要99%的时间,所以花相对较长的时间来生成优化的机器代码是值得的
我甚至不知道I2L本身,即使没有热点参与,是否花费了大量的时间。这是一条完全可以在寄存器中完成的指令,它是一个单字节操作码,而且随着流水线CPU的正常工作,我打赌在绝大多数情况下,这花费了几乎0额外的时间,它悄悄地介于其他操作之间考虑到热点问题,它很可能最终被完全优化掉
因此,问题就变成了,在你的目标硬件上,你拥有的特定版本的java(从oracle的java8到OpenJ9 14,这里有很多选择,它是CPU、操作系统和JVM版本的组合爆炸),它有多“坏”
也许这是一个通用的库,而您的目标是所有这些(许多版本、许多操作系统和CPU),没有简单的答案:使用诸如在许多平台上彻底测试性能之类的工具,或者假设开销可能会影响某些奇特的组合
但是,如果您可以将JVM和arch/OS限制得更低,那么这将变得更容易——只需JMH您的目标部署,现在您就知道了
不管它值多少钱,我打赌促销活动的成本不会在这里变得足够重要(更不用说在JMH中出现了)
*)在绝大多数CPU上,唯一可用的分支指令是“如果设置了某个标志,则转到代码中的此位置”-因此要编写IF,首先要编写GOTO a bunch ahead IF condition
,然后是else
代码,最后是转到IF块后的行,然后是IF代码
注意:在启动java可执行文件时,您可以使用一些-XX
参数,让它在某个方法热点时打印出来,甚至要求它打印它生成的机器代码,然后您可以通过反汇编程序查看真正重要的代码:CPU上到底运行了什么。即使有额外的指令,也不会因为CPU流水线而花费太多
NB2:在32位体系结构上,long通常比int的成本要高出很多,但是32位体系结构在这段时间里非常少见,所以我怀疑这一点在这里很重要。如果你想加快程序的速度,它会比int贵得多