Java代码优化,它会优化这一点

时间:2012-11-09 14:01:28

标签: java optimization

对于较新的编译器,我发现自己试图编写更容易阅读的代码,但如果我希望在引擎盖下完成的优化实际上并没有完成,那么可能会有更多的内存需求。 以此代码为例,非常简单的案例

while (scanner.hasNextLine() && !result)
{
    String line = scanner.nextLine();
    result = line.indexOf(searchString) >= 0;
}

假设(使用Eclipse Juno,Java 7)生成与

相同的字节代码是否公平?
while (scanner.hasNextLine() && !result)
{
    result = scanner.nextLine().indexOf(searchString) >= 0;
}

前者虽然2行代码减少了第二行的长度并使眼睛更容易。恕我直言 但它是否也会导致创建一个无关的String对象?我希望不是......

6 个答案:

答案 0 :(得分:7)

您无法逃避正在创建的String。将它分配给局部变量这一事实在这里是无关紧要的,实际上在字节码级别,这个事实甚至不会被注意到:在该级别,无论是否有显式变量,对结果的引用必须放在堆栈中要传递给链中的下一个方法调用。

您认为创建不必要的String实例的想法可能源于来自其他语言的本能,例如C:作业String s = ...仅将引用复制到唯一的字符串实例。这是因为所有Java对象都驻留在堆上,因此您始终需要显式复制对象以实际涉及另一个实例。例如,如果你写了String line = new String(scanner.nextLine()),那确实会创建一个不必要的String实例。

总而言之,您的代码的任何版本都不涉及优化,因此请仅根据样式首选项进行选择。

答案 1 :(得分:7)

一些一般原则:

  • 过早优化是毫无意义的
  • 在这些小问题中节省可读性是毫无意义的
  • 大多数时候,优化来自于更改算法及其复杂性
  • 在尝试优化不是算法的东西时,你永远不会是一个好的编译器
  • 确保任何优化只有两种方式:查看字节码或基准测试性能,其他一切通常都是猜测

在您的特定情况下:变量声明在optimitation方面不会改变任何内容,因为在两种情况下,字符串都由nextLine()实现并放在堆栈上,将其分配给变量(在字节码中消失) ,除非它是一个实例变量,因为它的用处仅适用于你的眼睛)不会改变任何东西。

答案 2 :(得分:2)

为什么不询问 Java类文件反汇编程序 - 每个JDK中包含的javap程序?

拥有以下源代码:

public class Foo {

    static void m1(Scanner scanner, String searchString, boolean result) {
        while (scanner.hasNextLine() && !result) {
            String line = scanner.nextLine();
            result = line.indexOf(searchString) >= 0;
        }
    }

    static void m2(Scanner scanner, String searchString, boolean result) {
        while (scanner.hasNextLine() && !result) {
            result = scanner.nextLine().indexOf(searchString) >= 0;
        }
    }
}

运行反汇编程序时:

javap -c Foo.class

您将获得以下字节码:

static void m1(java.util.Scanner, java.lang.String, boolean);
Code:
   0: goto          22
   3: aload_0
   4: invokevirtual #33                 // Method java/util/Scanner.nextLine:()Ljava/lang/String;
   7: astore_3
   8: aload_3
   9: aload_1
  10: invokevirtual #39                 // Method java/lang/String.indexOf:(Ljava/lang/String;)I
  13: iflt          20
  16: iconst_1
  17: goto          21
  20: iconst_0
  21: istore_2
  22: aload_0
  23: invokevirtual #45                 // Method java/util/Scanner.hasNextLine:()Z
  26: ifeq          33
  29: iload_2
  30: ifeq          3
  33: return

static void m2(java.util.Scanner, java.lang.String, boolean);
Code:
   0: goto          20
   3: aload_0
   4: invokevirtual #33                 // Method java/util/Scanner.nextLine:()Ljava/lang/String;
   7: aload_1
   8: invokevirtual #39                 // Method java/lang/String.indexOf:(Ljava/lang/String;)I
  11: iflt          18
  14: iconst_1
  15: goto          19
  18: iconst_0
  19: istore_2
  20: aload_0
  21: invokevirtual #45                 // Method java/util/Scanner.hasNextLine:()Z
  24: ifeq          31
  27: iload_2
  28: ifeq          3
  31: return

如果您比较两种方法的字节码,您会发现唯一差异m1包含以下两条额外说明:

7: astore_3
8: aload_3

这只是将对堆栈顶部对象的引用存储到局部变量中,没有别的。

修改

反汇编程序还可以显示方法的局部变量数:

javap -l Foo.class

哪个输出:

static void m1(java.util.Scanner, java.lang.String, boolean);
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0      34     0 scanner   Ljava/util/Scanner;
         0      34     1 searchString   Ljava/lang/String;
         0      34     2 result   Z
         8      14     3  line   Ljava/lang/String;

static void m2(java.util.Scanner, java.lang.String, boolean);
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0      32     0 scanner   Ljava/util/Scanner;
         0      32     1 searchString   Ljava/lang/String;
         0      32     2 result   Z
}

这基本上证实了上面唯一的区别 - m1方法只分配了一个局部变量 - String line它不会再创建任何对象,它只会再创建一个对分配方式的对象的引用。

答案 3 :(得分:2)

看似偏离主题,但这也会带来表现。

while (!result && scanner.hasNextLine())
{
    String line = scanner.nextLine();
    result = line.indexOf(searchString) >= 0;
}

答案 4 :(得分:1)

要回答您的问题,请执行scanner.nextLine().indexOf(searchString);你期望nextLine()做什么?你希望在哪个对象上执行indexOf()

你可能已经猜到它依赖于String;是的,这个String被创建了,是的,这个String被使用了。它(与您的猜测相反)必要

声明变量(String s)并分配值的操作与对象(new String("test"))的实例化相比没有任何成本。

换句话说,你想要达到的目标既不是有用的,也不是更有效的。


这里存在第二个问题,对于开发人员而言,这是一个更深层次的问题。尝试对这类代码进行优化,没有遇到实际问题,并且没有任何明显迹象表明此代码可能会使您的应用程序运行速度明显变慢,只是过早

大多数情况下,它会分散您想要实现的目标,并且会导致您为了优化而编写不可读的代码(甚至可能根本不是优化!)。

在你的特殊情况下(我很惊讶之前没有人提到它,这就是我写这个答案的原因)你的“优化”代码会让每个人的生活变得更糟。

想象一下,您的代码会运行,并且某些内容都会失败,并且您会在此行中获得NullPointerException

    result = scanner.nextLine().indexOf(searchString) >= 0;

现在必须手动调试该代码,以确定scannernull还是由于某种原因nextLine()返回{{1>而不是清楚地了解失败的原因}}

这个问题在您之前的代码中甚至不存在,但是这种早期优化和尝试使代码更紧凑以避免等待一些操作的愿望现在使您的代码全局变得更糟。

答案 5 :(得分:0)

编译器无论如何都会内联局部变量行,因此两者之间没有区别