这是一个两部分问题,但单个部分没有意义。字节码输出中的大量dup
指令是否是编写不良的代码的指示符?所有字节码指令的某个百分比定义大的位置。进一步如何重写生成dup
指令的代码?
答案 0 :(得分:7)
我们是在谈论您正在分析的javac
输出还是您自己的编译器/生成器?如果您从javac
生成的角度关注Java代码的质量 - 请忘掉它。首先,javac
产生次优的字节码并依赖JVM / JIT来进行所有优化(非常好的选择)。但仍然字节码可能比任何人都能快速提出的要好得多。它类似于询问C编译器生成的汇编代码的质量。
如果您自己生成字节码,过多的dup
可能看起来很糟糕,但也可能没有任何对性能的影响。请记住,字节码会转换为目标计算机上的程序集。 JVM是堆栈机器,但是现在大多数架构都是基于寄存器的。使用dup
的事实仅仅是因为某些字节码指令具有破坏性(读取时来自操作数堆栈的弹出值)。寄存器不会发生这种情况 - 您可以根据需要多次读取它们。以下面的代码为例:
new java/lang/Object
dup
invokespecial java/lang/Object <init> ()V
此处必须使用 dup
,因为invokespecial
会弹出操作数堆栈的顶部。在调用构造函数之后创建一个对象以便松开对它的引用听起来像个坏主意。但是在汇编中没有dup
,没有数据复制和复制。您将只有一个指向java/lang/Object
的CPU注册表。
换句话说,次优字节码在运行中被转换为“更优化”的汇编。只是......不要打扰。
答案 1 :(得分:2)
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 Compiler(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发挥其魔力之后),而两个堆栈单元可能在同一个缓存行上。
答案 2 :(得分:1)
如果您担心dup
及其关系对绩效的影响,请不要打扰。 JVM只是及时编译,所以它实际上不应该对性能产生任何影响。
就代码质量而言,有两个主要因素会导致Javac生成dup
指令。第一个是对象实例化,它是不可避免的。第二种是在表达式中使用立即值。如果你看到很多后者,它可能是质量差的代码,因为你通常不需要像源代码那样的复杂表达式(它的可读性较差)。
其他版本的dup
(dup_x1
,dup_x2
,dup2
,dup2_x1
和dup2_x2
)因对象实例化而特别成问题不使用那些,所以它几乎肯定意味着后者。当然,即便如此,这也不是一个大问题。这意味着源代码的可读性不尽如人意。
如果代码不是用Java编译的,那么所有的赌注都会关闭。指令的存在与否并没有真正告诉你,特别是在编译器执行编译时优化的语言中。