views:

407

answers:

9

Hello! I want to ask which piece of code is more efficient in Java? Code 1:

void f()
{
 for(int i = 0 ; i < 99999;i++)
 {
  for(int j = 0 ; j < 99999;j++)
  {
   //Some operations
  }
 }

}

Code 2:

void f()
{
 int i,j;
 for(i = 0 ; i < 99999;i++)
 {
  for(j = 0 ; j < 99999;j++)
  {
   //Some operations
  }
 }

}

My teacher said that second is better, but I can't agree that opinion.

+6  A: 

The second is worse.

Why? Because the loop variable is scoped outside the loop. i and j will have a value after the loop is done. Generally that isn't what you want. The first scopes the loop variables so it's only visible within the loop.

cletus
It compiles fine for me.
Marcelo Cantos
Agreed - any gain in efficiency is not worth possible issue because of reuse of i and j. However, funny enough, in C++ not all compilers enforce the scope of i and j if declared in loop
David Relihan
@Marcelo - @cletus never said anything about compilation errors.
Stephen C
Stephen, he did in the first version. I don't know why it doesn't appear as an edit, but I saw it. The remark was that the second version doesn't even compile.
Marcelo Cantos
Has it been edited? Both codes work fine in my eclipse... Edit: ok, ok
Random
+1 Minimize the scop of local variables (J. Bloch)
Helper Method
A: 

Second is better for speed.

Reason is that in the first case, the scope of j is limited to the inner for loop.

As such, the moment, the inner loop is completed, the memory for j is de-allocated, and again allocated for the next iteration of the outer loop.

Because the memory allocation and deallocation take some time, even though it's on stack, the performance of the first one is slower.

MasterGaurav
There is no such thing as stack "allocation". The code simply opts to use a certain portion of available stack for the purpose of storing the int.
Marcelo Cantos
Yes, the answer is wrong. There's no allocation and deallocation going on in the JVM
HH
The variable j goes out scope in the first case... so, what does it mean?I agree there's nothing as stack allocation... but then, using the value on the stack and making it out of scope... is it only a compile-time stuff?
MasterGaurav
MasterGaurav, I explained that in my answer. The JVM allocates once enough memory for all local variables. When a local variable goes out of scope, the same memory slot is used for another local variable
HH
Thanks HH!My understanding mistake :)
MasterGaurav
+29  A: 

IT. DOESN'T. MAKE. A. DIFFERENCE.

Stop micro-optimizing. These little tricks don't make programs run much faster.

Concentrate on big picture optimizations and writing readable code.

Declare variables where they make sense, and where it helps understanding the semantics of the overall code in the bigger context, not because you think it's faster one place over another.

polygenelubricants
Agreed! Your focus should be why you're doing a nested loop and what operations are taking place INSIDE the nested loop. Not the two wimpy ints on the outside!
basszero
Still, the question remains very interesting. Why isn't Java compiler optimizing away both loops because they have no side effects? Even if `// some operations` was more than a comment, it's an easy micro-optimization for javac so both variants should be equally fast.
Alexandru
@Alexandru I don't follow - if "// some operations" is more than a comment, how do you expect to optimize it away?
CPerkins
`for (int i = 0; i < 999999; ++i) {}` is useless and can be removed. Stephen C already pointed that out in another answer, though I'm puzzled why java chooses to optimize a lot at run time and less at compile time.
Alexandru
@Alexandru: `.java` to bytecode is unoptimized. The most effective optimization happens at JIT level, not at `javac` level.
polygenelubricants
I agree with the sentiment of not micro-optimizing but there is one thing that matters here: variable scope. That can create real problems.
cletus
@cletus: presumably the two options are presented in a context where they're compatible semantically (or else it's a moot point which one is faster), thus variable scoping doesn't matter for correctness sake. It matters A LOT in readability sake, of course, and rather than pointing out which one is better and end it there, I feel like OP needs to figure it out on his/her own. Hopefully in the process OP will learn what really matters.
polygenelubricants
@Alexandru - I understand that if it's an empty loop, it should be optimized away. I'm asking about your comment above "Even if // some operations was more than a comment...": so if there's code there, and it's more than a comment what would you expect would be optimized away? What micro-optimization are you talking about?
CPerkins
@Alexandru debuging, fast compiling, plattform dependent optimisations, usage based optimisation are some reasons.
josefx
@CPerkins Sorry for not begin clear. The micro-optimization that you have shown (moving i,j out of the loop) should be done by java easily (and it is by JVM).
Alexandru
+2  A: 

I would guess that there is absolutely no difference in efficiency for any half-decent JVM implementation.

Marcelo Cantos
A: 

First off, yes your teacher is wrong, the second code is not better. What does better mean anyway? This is because in any normal loop the operations inside the loop body are the part, that are time consuming. So Code 2 is just a micro optimization, which doesn't add enough speed (if any) to justify the bad readability of the code.

ablaeul
+2  A: 

No, It does not make a difference at all (speed wise). They both get compiled into the same code. And there's no allocation and deallocation going on like MasterGaurav said.

When the method starts, the JVM allocates enough memory slots for all local variables, and no more allocations occurs until the end of the method.

The only small tiny insignificant difference (other than the scope), is that with the first example, the memory allocated for i & j can be reused for other variables. Therefore, the JVM will allocates fewer memory slots for this method (well, yous saved some bits)

HH
+9  A: 

I would prefer the first over the second because it keeps the loop variables out of the way of the rest of the code in the method. Since they're not visible outside of the loop, you can't accidentally refer to them later on.

The other answers are right, too: don't worry about this sort of thing for performance. But do think about it for code readability reasons, and for communicating programmer intent to the next person who comes along. This is much more important than micro-optimization concerns.

Now, that's at the Java language (as in Java Language Specification) level. At the Java Virtual Machine level, it makes absolutely no difference which of those two you use. The locals are allocated in exactly the same way.

If you're not sure, you can always compile it and see what happens. Let's make two classes, f1 and f2, for the two versions:

$ cat f1.java
public class f1 {
  void f() {
    for(int i = 0 ; i < 99999;i++) {
      for(int j = 0 ; j < 99999;j++) {
      }
    }
  }
}

$ cat f2.java
public class f2 {
  void f() {
    int i, j;
    for(i = 0 ; i < 99999;i++) {
      for(j = 0 ; j < 99999;j++) {
      }
    }
  }
}

Compile them:

$ javac f1.java
$ javac f2.java

And decompile them:

$ javap -c f1 > f1decomp
$ javap -c f2 > f2decomp

And compare them:

$ diff f1decomp f2decomp
1,3c1,3
< Compiled from "f1.java"
< public class f1 extends java.lang.Object{
< public f1();
---
> Compiled from "f2.java"
> public class f2 extends java.lang.Object{
> public f2();

There's absolutely no difference in the bytecode.

Anonymouse
+1 for the decompilation demonstration.
tylermac
+12  A: 

Beware the perils of micro-benchmarking!!!

I took the code, wrapped a method around the outside, and ran that 10 times in a loop. Results:

50, 3, 
3, 0, 
0, 0, 
0, 0, 
....

Without some actual code in the loops, the compilers are able to figure out that the loops do no useful work and optimize them away completely. Given the measured performance, I suspect that this optimization might have been done by javac.

Lesson 1: Compilers will often optimize away code that does useless "work". The smarter the compiler is, the more likely it is that this sort of thing will happen. If you don't allow for this in the way you code it, a benchmark can be meaningless.

So I then added the following simple calculation in both loops if (i < 2 * j) longK++; and made the test method return the final value of longK. Results:

32267, 33382,
34542, 30136,
12893, 12900,
12897, 12889,
12904, 12891,
12880, 12891,
....

We have obviously stopped the compilers optimizing the loop away. But now we see the effects of JVM warmups in (in this case) the first two pairs of loop iterations. The first two pairs of iterations (one method call) are probably run purely in interpreted mode. And it looks the third iteration might actually be running in parallel with the JIT. By the third pair of iterations, we are most likely running pure native code. And from then on, the difference between the timing of the two versions of loop is simply noise.

Lesson 2: always take into account the effect of JVM warmup. This can seriously distort benchmark results, both micro and macro.

Conclusion - once the JVM has warmed up, there is no measurable difference between the two versions of the loop.

Stephen C
+1 for good explanation, example of the effects of the in process optimization of the JVM/JIT. My own thought, people often seem to forget that the compiled code does not have to be the written code verbatim if it is computationally equivalent, i.e. the classic example of recursive fibonnaci translated to a simple loop by a smart compiler (before it even gets to the JVM/JIT).
M. Jessup
@Stephen: I'm pretty sure that the compiler (`javac`) is NOT responsible for the optimization of the first results (zero times). That's done by the JVM: I only got zeros if running with the `-server` option, *normal* times (about 10 s) otherwise. Inspecting the byte code does not show any optimization (despite reusing the same address for the local variables for both loops, no matter where they were declared)
Carlos Heuberger
A: 

There is one more aspect, which is very important about the two different versions:

While in variant 2 there are only two temporary objects created (i and j), variant 1 will create 100000 objects (1 x i and 999999 x j). Probably your compiler is going to optimize this, but this is something you can´t be sure of. If it doesn´t optimize it, the garbage collection will behave significantly worse.

Georg Edelmann
"int" is not an object type.
Thorbjørn Ravn Andersen