这是一个由两部分组成的问题,但单个部分没有意义。字节码输出中的大量dup
指令是否表明代码编写不佳?其中 large 由所有字节码指令的某个百分比定义。进一步,如何重写生成dup
指令的代码?
3 回答
我们是在谈论javac
您正在分析的输出还是您自己的编译器/生成器?如果您从javac
生成的角度担心 Java 代码的质量,那就别管它了。首先javac
生成次优字节码并依赖 JVM/JIT 进行所有优化(非常好的选择)。但是,字节码可能比任何一个可以快速想出的东西都要好得多。这类似于询问 C 编译器生成的汇编代码的质量。
如果您自己生成字节码,过多的字节码dup
可能看起来很糟糕,但也可能不会对性能产生任何影响。请记住,字节码在目标机器上被翻译成程序集。JVM 是堆栈机器,但如今大多数架构都是基于寄存器的。使用的事实dup
只是因为某些字节码指令具有破坏性(读取时从操作数堆栈弹出值)。寄存器不会发生这种情况 - 您可以根据需要多次读取它们。以下面的代码为例:
new java/lang/Object
dup
invokespecial java/lang/Object <init> ()V
dup
必须在这里使用,因为invokespecial
弹出操作数堆栈的顶部。在调用构造函数后创建一个对象只是为了释放对它的引用听起来是个坏主意。但是在汇编中没有dup
,没有数据复制和复制。您将只有一个 CPU 注册表指向java/lang/Object
.
换句话说,次优字节码被即时翻译成“更优”的汇编。只是……不要打扰。
该dup
指令只是复制操作数堆栈的顶部元素。如果编译器知道它将在相对较短的跨度内多次使用一个值,它可以选择复制该值并将其保存在操作数堆栈中,直到需要为止。
您看到的最常见情况之一dup
是创建对象并将其存储在变量中时:
Foo foo = new Foo();
运行javap -c
,你得到以下字节码:
0: new #1; //class Foo
3: dup
4: invokespecial #23; //Method "<init>":()V
7: astore_1
英文:new
操作创建Foo
对象的新实例,并invokespecial
执行Foo
构造函数。由于您需要堆栈上的引用来调用构造函数并存储在变量中,因此使用它很有意义dup
(特别是因为替代方案,存储在变量中然后检索运行 ctor,可能违反 Java记忆模型)。
这是我期望的 Oracle Java 编译器 (1.6)没有使用的情况:dup
int x = 12;
public int bar(int z)
{
int y = x + x * 3;
return y + z;
}
我希望编译器dup
的值为x
,因为它在表达式中出现多次。相反,重复从对象加载值的代码:
0: aload_0
1: getfield #12; //Field x:I
4: aload_0
5: getfield #12; //Field x:I
8: iconst_3
9: imul
10: iadd
我会预料到,dup
因为从对象中检索值相对昂贵(即使在 Hotspot 发挥了魔力之后),而两个堆栈单元可能位于同一缓存行上。
如果您担心dup
它对性能的影响及其关系,请不要打扰。JVM 进行即时编译,因此它实际上不应该对性能产生任何影响。
就代码质量而言,有两个主要因素会导致 Javac 生成dup
指令。第一个是对象实例化,这是不可避免的。第二个是表达式中立即数的某些使用。如果您看到很多后面的内容,则可能是质量较差的代码,因为您通常不希望在源代码中出现这样的复杂表达式(它的可读性较差)。
dup
( dup_x1
, dup_x2
, dup2
, dup2_x1
, and )的其他版本dup2_x2
尤其成问题,因为对象实例化不使用这些版本,因此几乎可以肯定意味着后者。当然即使这样也不是什么大问题。这意味着源代码没有应有的可读性。
如果代码不是从 Java 编译的,那么所有的赌注都没有了。指令的存在与否并不能真正告诉您太多,尤其是在编译器执行编译时优化的语言中。