简体   繁体   English

具有原始类型的Java代码的效率

[英]Efficiency of Java code with primitive types

I want to ask which piece of code is more efficient in Java? 我想问一下Java中哪一段代码效率更高? Code 1: 代码1:

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

}

Code 2: 代码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. 我的老师说第二个更好,但我不同意这个意见。

IT. 它。 DOESN'T. 不。 MAKE. 使。 A. DIFFERENCE. A.差异。

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. 声明它们有意义的变量,以及它有助于在更大的上下文中理解整个代码的语义,而不是因为你认为它比另一个更快。

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. 现在,这是Java语言(如Java语言规范)级别。 At the Java Virtual Machine level, it makes absolutely no difference which of those two you use. 在Java虚拟机级别,它使用这两者中的哪一个完全没有区别。 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: 让我们为两个版本制作两个类f1和f2:

$ 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. 字节码完全没有区别。

Beware the perils of micro-benchmarking!!! 谨防微基准测试的危险!

I took the code, wrapped a method around the outside, and ran that 10 times in a loop. 我拿了代码,在外面包裹了一个方法,并在循环中运行了10次。 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 . 鉴于测量的性能,我怀疑这种优化可能是由javac完成的。

Lesson 1: Compilers will often optimize away code that does useless "work". 第1课:编译器通常会优化掉无用“工作”的代码。 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++; 所以我在两个循环中添加了以下简单计算if (i < 2 * j) longK++; and made the test method return the final value of longK . 并使测试方法返回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. 但现在我们看到JVM预热(在这种情况下)前两对循环迭代的影响。 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. 它看起来第三次迭代实际上可能与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. 第2课:始终考虑JVM预热的影响。 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. 结论 - 一旦JVM预热,两个版本的循环之间就没有可测量的差异。

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. 循环完成后, ij将有一个值。 Generally that isn't what you want. 一般来说,这不是你想要的。 The first scopes the loop variables so it's only visible within the loop. 第一个范围是循环变量,因此它只在循环中可见。

我猜想任何半体面JVM实现的效率绝对没有区别。

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. 就像MasterGaurav所说的那样,没有任何分配和解除分配。

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. 当方法启动时,JVM为所有局部变量分配足够的内存槽,并且在方法结束之前不再发生分配。

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. 唯一的微不足道的微不足道的差异(除了范围)是第一个例子,为i&j分配的内存可以重用于其他变量。 Therefore, the JVM will allocates fewer memory slots for this method (well, yous saved some bits) 因此,JVM将为此方法分配更少的内存插槽(好吧,你保存了一些位)

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. 所以Code 2只是一个微优化,它没有增加足够的速度(如果有的话)来证明代码的可读性差。

Second is better for speed. 第二是速度更好。

Reason is that in the first case, the scope of j is limited to the inner for loop. 原因是在第一种情况下, j的范围仅限于内部for循环。

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. 这样,当时内循环完成, j的存储器被解除分配,并再次分配给外循环的下一次迭代。

Because the memory allocation and deallocation take some time, even though it's on stack, the performance of the first one is slower. 因为内存分配和释放需要一些时间,即使它在堆栈上,第一个的性能也较慢。

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 xi and 999999 xj). 在变体2中,只创建了两个临时对象(i和j),变体1将创建100000个对象(1 xi和999999 xj)。 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. 如果它没有优化它,垃圾收集将表现得更糟。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM