Har*_*Joy 224 java optimization while-loop
为什么以下工作正常?
String str;
while (condition) {
str = calculateStr();
.....
}
Run Code Online (Sandbox Code Playgroud)
但据说这个是危险的/不正确的:
while (condition) {
String str = calculateStr();
.....
}
Run Code Online (Sandbox Code Playgroud)
是否有必要在循环外声明变量?
Pri*_*osK 286
我比较了这两个(类似)示例的字节代码:
我们来看看1.例子:
package inside;
public class Test {
public static void main(String[] args) {
while(true){
String str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
Run Code Online (Sandbox Code Playgroud)
之后javac Test.java,javap -c Test你会得到:
public class inside.Test extends java.lang.Object{
public inside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
Run Code Online (Sandbox Code Playgroud)
我们来看看2.例子:
package outside;
public class Test {
public static void main(String[] args) {
String str;
while(true){
str = String.valueOf(System.currentTimeMillis());
System.out.println(str);
}
}
}
Run Code Online (Sandbox Code Playgroud)
之后javac Test.java,javap -c Test你会得到:
public class outside.Test extends java.lang.Object{
public outside.Test();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2; //Method java/lang/System.currentTimeMillis:()J
3: invokestatic #3; //Method java/lang/String.valueOf:(J)Ljava/lang/String;
6: astore_1
7: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
10: aload_1
11: invokevirtual #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
14: goto 0
}
Run Code Online (Sandbox Code Playgroud)
观察结果表明,这两个例子没有区别.这是JVM规范的结果......
但是在最佳编码实践的名义下,建议在尽可能小的范围内声明变量(在此示例中,它位于循环内部,因为这是使用变量的唯一位置).
Mik*_*kis 279
在您的例子我相信str是不会在外部使用while循环,否则你就不会问这个问题,因为它声明的内部while循环不会是一个选项,因为它不会编译.
所以,既然str是不使用外循环,在尽可能小的范围str是内 while循环.
所以,答案是着重那str绝对应该被while循环内声明.没有ifs,没有ands,没有buts.
唯一可能违反此规则的情况是,由于某种原因,必须从代码中挤出每个时钟周期至关重要,在这种情况下,您可能需要考虑在外部作用域中实例化某些内容并重新使用它而不是在内部范围的每次迭代中重新实例化它.但是,由于java中字符串的不变性,这不适用于您的示例:str的新实例将始终在循环的开头创建,并且必须在其结尾处丢弃,因此没有可能优化那里.
编辑:(在答案中注入我的评论)
在任何情况下,正确的做法是正确编写所有代码,为产品建立性能要求,根据此要求测量最终产品,如果不满足,则优化.通常最终会发生的事情是,您可以找到方法在几个地方提供一些很好的和正式的算法优化,使我们的程序满足其性能要求,而不必遍及整个代码库,调整和破解为了在这里和那里挤压时钟周期.
Cha*_*har 26
声明最小范围内的对象可提高可读性.
对于今天的编译器而言,性能并不重要.(在这种情况下)
从维护的角度来看,第二种选择更好.
在尽可能最窄的范围内在同一位置声明和初始化变量.
正如唐纳德·欧文·克努特所说:
"我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源"
ie)程序员让性能考虑影响一段代码设计的情况.这可能导致设计不像以前那样干净或代码不正确,因为优化会使代码变得复杂,并且程序员会因优化而分散注意力.
请跳到更新的答案...
对于那些关心性能的人,请取出System.out并将循环限制为1个字节.使用double(测试1/2)和使用String(3/4),下面给出了Windows 7 Professional 64位和JDK-1.7.0_21的经过时间(以毫秒为单位).字节码(下面也给出了test1和test2)并不相同.我懒得用可变的和相对复杂的物体进行测试.
双
Test1拍摄:2710毫秒
Test2拍摄:2790毫秒
字符串(在测试中只用字符串替换double)
Test3采用:1200毫秒
Test4采用:3000毫秒
编译并获取字节码
javac.exe LocalTest1.java
javap.exe -c LocalTest1 > LocalTest1.bc
public class LocalTest1 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
double test;
for (double i = 0; i < 1000000000; i++) {
test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
public class LocalTest2 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
for (double i = 0; i < 1000000000; i++) {
double test = i;
}
long finish = System.currentTimeMillis();
System.out.println("Test1 Took: " + (finish - start) + " msecs");
}
}
Compiled from "LocalTest1.java"
public class LocalTest1 {
public LocalTest1();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore 5
7: dload 5
9: ldc2_w #3 // double 1.0E9d
12: dcmpg
13: ifge 28
16: dload 5
18: dstore_3
19: dload 5
21: dconst_1
22: dadd
23: dstore 5
25: goto 7
28: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
31: lstore 5
33: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
36: new #6 // class java/lang/StringBuilder
39: dup
40: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
43: ldc #8 // String Test1 Took:
45: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
48: lload 5
50: lload_1
51: lsub
52: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
55: ldc #11 // String msecs
57: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
60: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
63: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
66: return
}
Compiled from "LocalTest2.java"
public class LocalTest2 {
public LocalTest2();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
3: lstore_1
4: dconst_0
5: dstore_3
6: dload_3
7: ldc2_w #3 // double 1.0E9d
10: dcmpg
11: ifge 24
14: dload_3
15: dstore 5
17: dload_3
18: dconst_1
19: dadd
20: dstore_3
21: goto 6
24: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J
27: lstore_3
28: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
31: new #6 // class java/lang/StringBuilder
34: dup
35: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
38: ldc #8 // String Test1 Took:
40: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: lload_3
44: lload_1
45: lsub
46: invokevirtual #10 // Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
49: ldc #11 // String msecs
51: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
54: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
57: invokevirtual #13 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
60: return
}
Run Code Online (Sandbox Code Playgroud)
将性能与所有JVM优化进行比较真的不容易.但是,这有点可能.Google Caliper中更好的测试和详细结果

这与上面的代码不同.如果你只编写一个虚拟循环JVM跳过它,那么至少你需要分配并返回一些东西.在Caliper文档中也建议这样做.
@Param int size; // Set automatically by framework, provided in the Main
/**
* Variable is declared inside the loop.
*
* @param reps
* @return
*/
public double timeDeclaredInside(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Declaration and assignment */
double test = i;
/* Dummy assignment to fake JVM */
if(i == size) {
dummy = test;
}
}
return dummy;
}
/**
* Variable is declared before the loop.
*
* @param reps
* @return
*/
public double timeDeclaredBefore(int reps) {
/* Dummy variable needed to workaround smart JVM */
double dummy = 0;
/* Actual test variable */
double test = 0;
/* Test loop */
for (double i = 0; i <= size; i++) {
/* Assignment */
test = i;
/* Not actually needed here, but we need consistent performance results */
if(i == size) {
dummy = test;
}
}
return dummy;
}
Run Code Online (Sandbox Code Playgroud)
总结:declaredBefore表示性能更好 - 非常小 - 并且它违反了最小范围原则.JVM实际上应该为你做这件事
小智 7
解决此问题的一个方法是提供封装while循环的变量范围:
{
// all tmp loop variables here ....
// ....
String str;
while(condition){
str = calculateStr();
.....
}
}
Run Code Online (Sandbox Code Playgroud)
当外部范围结束时,它们将自动取消引用.
如果你不需要使用strwhile循环(范围相关),那么第二个条件即
while(condition){
String str = calculateStr();
.....
}
Run Code Online (Sandbox Code Playgroud)
更好,因为如果你在堆栈上定义一个对象,只有它condition是真的.即如果你需要它也可以使用它