9

This is a two part question, but wouldn't make sense by the individual pieces. Is a large number of dup instructions within the bytecode output an indicator of poorly written code? Where large is defined by some percentage of all bytecode instructions. Further how does one go about rewriting code that generates a dup instruction?

8
  • Out of curiosity, what would provoke such a question? Commented Feb 15, 2013 at 20:10
  • I suppose you have checked the links on the right: stackoverflow.com/questions/8594657/… and stackoverflow.com/questions/12438567/java-bytecode-dup Commented Feb 15, 2013 at 20:12
  • @assylias yes of course. However, they do not address my specific questions. Commented Feb 15, 2013 at 20:13
  • @delnan this question came about when I became curious about bytecode and how some legacy code was written. As such there are a plethora of dup instructions in said bytecode output which screams to me that something is greatly amiss. However, even after rewriting it in a less procedural way there are still many dup instructions. Commented Feb 15, 2013 at 20:14
  • dup just means a value is re-used, I don't see how that says anything about code-quality Commented Feb 15, 2013 at 20:19

3 Answers 3

8

Are we talking about javac output you are analyzing or your own compiler/generator? If you are concerned about the quality of your Java code from the perspective of what javac produces - forget about it. First of all javac produces suboptimal bytecode and relies on JVM/JIT to do all the optimizations (very good choice). But still bytecode is probably much better than anything one can come up with quickly. It's similar to asking about the quality of assembly code generated by C compiler.

If you are generating bytecode yourself, excessive number of dup may look bad, but as well it might not have any impact on performance. Remember that bytecode is translated to assembly on target machine. JVM is stack machine but most architectures these days are register based. The fact that dup is used is only because some bytecode instructions are destructive (pop value from operand stack when reading). This doesn't happen with registers - you can read them as many times as you want. Take the following code as an example:

new java/lang/Object
dup
invokespecial java/lang/Object <init> ()V

dup must be used here because invokespecial pops top of the operand stack. Creating an object just to loose a reference to it after calling constructor sounds like a bad idea. But in assembly there is no dup, no data copying and duplication. You will just have a single CPU registry pointing to java/lang/Object.

In other words suboptimal bytecode is translated into "more optimal" assembly on the fly. Just... don't bother.

Sign up to request clarification or add additional context in comments.

7 Comments

Hmm, do you have some resources that state why? I have done my fair share of research and it seems that understanding the bytecode instruction set is critical for understanding what the program does at runtime. If I can find the thesis that calls this out I will post it as a link.
@Woot4Moo: what kind of resources are you asking for? I agree that understanding bytecode is very important. I'm just saying that dup might not really trigger single CPU instruction. It's just an abstraction that will go away during JIT compilation.
That is the type of resource I am looking for. Where it proves or at least alludes to in some cases that JIT tosses it away. And i will accept a response of the big bytecode spec if that is indeed where it resides.
@Woot4Moo: it heavily depends on target platform. If you are familiar with low-level assembly, you can try enabling -XX:+PrintOptoAssembly while running your app with lots of dup and search for dup-like operations. I am pretty sure they will all be gone since reading CPU registry is not destructive.
@Woot4Moo: you mean: rewriting Java code to limit the number of dup instructions? It's like rewriting C++ to reduce the number of mov assembly instructions. However in C++ it might actually have some sense while, as I said, dup most likely disappears from assembly. Do you have any benchmark/evidence that dup is causing any noticeable performance drop?
|
2

The dup instruction simply duplicates the top element of the operand stack. If the compiler knows that it's going to use a value multiple times within a relatively short span, it can choose to duplicate the value and hold it on the operand stack until needed.

One of the most common cases where you see dup is when you create an object and store it in a variable:

Foo foo = new Foo();

Running javap -c, you get the following bytecode:

0:  new #1; //class Foo
3:  dup
4:  invokespecial   #23; //Method "<init>":()V
7:  astore_1

In English: the new operation creates a new instance of the Foo object, and the invokespecial executes the Foo constructor. Since you need the reference on the stack to invoke the constructor and also to store in the variable, it makes a lot of sense to use dup (especially since the alternative, storing in the variable and then retrieving to run the ctor, could violate the Java memory model).

Here's a case where where the Oracle Java Compiler (1.6) didn't use dup when I would expect it to:

int x = 12;

public int bar(int z)
{
    int y = x + x * 3;
    return y + z;
}

I'd expect the compiler to dup the value of x, since it appears multiple times in the expression. Instead, itemitted code that repeatedly loaded the value from the object:

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

I would have expected the dup because it's relatively expensive to retrieve a value from an object (even after Hotspot does its magic), whereas two stack cells are likely to be on the same cache line.

12 Comments

and the reasoning why it is cheaper?
"whereas two stack cells are likely to be on the same cache line"
Although, for all I know, Hotspot recognizes the duplicated getfield calls, recognizes that the object is not volatile, and caches the field value in a register.
Javac basically doesn't optimize. The bytecode is a near direct translation of what you write.
"The attitude of fixing poorly written code [...] Perhaps I am misunderstanding what you mean". Yes, you're misunderstanding. I am referring to a mindless focus on reducing the recurrence of a single opcode. As for the ad hom attack that follows ... do you feel better now?
|
1

If you're worried about the impact of dup and its relations on performance, don't bother. The JVM does just in time compilation, so it shouldn't actually make any difference in performance.

As far as quality of code, there are two main things that will cause Javac to generate dup instructions. The first is object instantiation, where it is unavoidable. The second is certain uses of immediate values in expressions. If you see a lot of the later, it could be poor quality code, since you don't usually want complicated expressions like that in your source code (it's less readable).

The other versions of dup (dup_x1, dup_x2, dup2, dup2_x1, and dup2_x2) are especially problematic since object instantiation doesn't use those, so it almost certainly means the later. Of course even then it's not a huge problem. All it means is that the source code isn't as readable as it could be.

If the code isn't compiled from Java, all bets are off. The presence or absence of instructions doesn't really tell you much, especially in the languages whose compilers perform compile time optimization.

1 Comment

The legacy code is very very procedural in nature and unreusable ( > 500 line functions and such)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.