【问题标题】:When to use local variable instead of method?何时使用局部变量而不是方法?
【发布时间】:2015-12-06 16:00:11
【问题描述】:

什么时候应该使用局部变量而不是方法?我使用规则,如果方法在特定代码块中使用两次,则应将其分配给局部变量以减少计算量,但我不确定 JVM 是否没有优化它,所以我想得到真正的答案.假设对于给定的上下文,该方法每次都会返回相同的结果。

例如:

private boolean someMethod() {
    return true;
}

private boolean otherMethod() {
    if (someMethod()) {
      System.out.println(1);
    }
    // other logic
    if (someMethod()) {
        System.out.println(2);
    }
}

我会重构为:

private boolean someMethod() {
    return true;
}
private boolean otherMethod() {
    boolean localVar = someMethod();
    if (localVar) {
      System.out.println(1);
    }
    // other logic
    if (localVar) {
        System.out.println(2);
    }
}

这是正确的方法吗?

【问题讨论】:

  • methodvariable 是两个不同的东西。
  • 答案是dependsmethod
  • 我已经更新了问题 - 让我们假设对于给定的上下文,该方法将始终返回相同的结果。因此,对于我在 otherMethod() 上下文中的示例, someMethod() 将始终给出相同的结果
  • 我不是 Java 内部(编译器、字节码等)方面的专家,但根据我对这些方面的有限了解,我会说 这个特定的代码(即在哪里该方法是私有的,只有return true,并且第二个示例中的布尔变量是本地的)将被编译器优化。

标签: java


【解决方案1】:

首先你应该考虑

  • 可读性(哪种方式更易于理解和维护)。
  • 值一致性。如果您习惯于将值缓存在变量中,并且在某些情况下值发生了变化,那么您可能会遇到难以发现的错误。

然后是性能问题。对于这种琐碎的情况可能相同或效率较低。即使你使用了这个变量 1000 次。

如果您只是返回一个常量或引用,编译器可能会翻译if (someMethod()) -> if (true)(请参阅Method inlining)。


更新

只是为了娱乐目的(玩弄自制的袖珍基准)(不像 JMH 那样严肃)。

  • 表示对于简单的情况,创建一个变量来缓存方法返回常量的结果是没有好处的。
  • 奇怪的是,我检查了生成的字节码 (jdk 1.8.0_31) 和 Inlining$2#executecalledStaticMethod 进行了 3 次调用(因此编译时没有内联)。我一定遗漏了一些东西(Eclipse 有自己的编译器?选项?误解?)并且虚拟机 JIT 优化器做得非常好。

源代码 (Inlining.java)。包含几乎无害附加的基准类

// https://stackoverflow.com/questions/32500628/when-to-use-local-variable-instead-of-method
public class Inlining {
public static int calledStaticMethod() {
    return 0;
}

public static void dumpValue(Object o) { // Fool compiler/JVM that value is going somewhere

}

public static void main(String[] args) {
    float iterations = Benchmark.iterationsFor10sec(new Benchmark("cacheCall") {
        @Override
        public void execute(long iterations) {
            int i1,i2,i3 = 0;
            for (int i = 0; i < iterations; i++) {
                int localVar = calledStaticMethod();
                i1 = localVar;
                i2 = i1 + localVar;
                i3 = i2 + localVar;
            }
            dumpValue((Integer)i3);
        }
    });
    System.out.printf("cacheCall: %10.0f\n", iterations);

    iterations = Benchmark.iterationsFor10sec(new Benchmark("staticCall") {
        @Override
        public void execute(long iterations) {
            int i1,i2,i3 = 0;
            for (int i = 0; i < iterations; i++) {
                i1 = calledStaticMethod();
                i2 = i1 + calledStaticMethod();
                i3 = i2 + calledStaticMethod();
            }
            dumpValue((Integer)i3);
        }
    });
    System.out.printf("staticCall: %10.0f\n", iterations);

    // borderline for inlining, as instance methods might be overridden.
    iterations = Benchmark.iterationsFor10sec(new Benchmark("instanceCall") {
        public int calledInstanceMethod() { return calledStaticMethod(); }
        @Override
        public void execute(long iterations) {
            int i1,i2,i3 = 0;
            for (int i = 0; i < iterations; i++) {
                i1 = calledInstanceMethod();
                i2 = i1 + calledInstanceMethod();
                i3 = i2 + calledInstanceMethod();
            }
            dumpValue((Integer)i3);
        }
    });
    System.out.printf("instanceCall: %10.0f\n", iterations);
}

}

abstract class Benchmark {
private String name;
public Benchmark(String s) { name = s; }
public String getName() { return name; }
public abstract void execute(long iterations);
public static float iterationsFor10sec(Benchmark bm) {
    long t0 = System.nanoTime();
    long ellapsed = 0L;
    // Calibration. Run .5-1.0 seconds. Estimate iterations for .1 sec
    final long runtimeCalibrate = (long)0.5e9; // half second
    long iterations = 1L;
    while (ellapsed < runtimeCalibrate) {
        bm.execute(iterations);         
        iterations *= 2;
        ellapsed = System.nanoTime() - t0;
    }
    iterations--; // Actually we executed 2^N - 1.
    int innerIterations = (int) ((double)iterations * 1e8 /* nanos/inner */ / ellapsed);
    if (innerIterations < 1) { innerIterations = 1; }
    // run rest of the time
    final long runtimeTotal = (long)1e10;
    // outer loop
    while (ellapsed < runtimeTotal) {
        // delegate benchmark contains inner loop
        bm.execute(innerIterations);
        iterations += innerIterations;
        ellapsed = System.nanoTime() - t0;
    }
    // execution time might exceed 10 seconds. rectify number of iterations
    return (float)iterations * 1e10f /* nanos total */ / (float)ellapsed;

}
}

输出:

cacheCall: 21414115328
staticCall: 21423159296
instanceCall: 21357850624

【讨论】:

    【解决方案2】:

    仅供参考,我做了一个小微基准测试,如下所示:

    public class Main {
    
    public static void main(String[] args) {
        Method method = new Method();
        Variable variable = new Variable();
    
        long startMethod;
        long startVariable;
        long methodDuration;
        long variableDuration;
    
        startMethod = System.currentTimeMillis();
        method.someMethod();
        methodDuration = System.currentTimeMillis() - startMethod;
    
        startVariable = System.currentTimeMillis();
        variable.someMethod();
        variableDuration = System.currentTimeMillis() - startVariable;
    
        System.out.println("methodDuration: " + methodDuration);
        System.out.println("variableDuration: " + variableDuration);
        System.exit(0);
    }
    }
    
    public class Method {
    
    public void someMethod() {
        long x = otherMethod();
        long y = otherMethod();
        long z = otherMethod();
        x--;
        y--;
        z--;
        System.out.println(x);
        System.out.println(y);
        System.out.println(z);
    }
    
    private long otherMethod() {
        long result = 0;
        for (long i = 1; i < 36854775806L / 2; i++) {
            i = i * 2;
            i = i / 2;
            result = i;
        }
        return result;
    }
    }
    
    public class Variable {
    
    public void someMethod() {
        long x = otherMethod();
        long y = x;
        long z = x;
        x--;
        y--;
        z--;
        System.out.println(x);
        System.out.println(y);
        System.out.println(z);
    }
    
    private long otherMethod() {
        long result = 0;
        for (long i = 1; i < 36854775806L / 2; i++) {
            i = i * 2;
            i = i / 2;
            result = i;
        }
        return result;
    }
    }
    

    我已经运行了两次,结果如下:

    methodDuration: 116568
    variableDuration: 37674
    methodDuration: 116657
    variableDuration: 37679
    

    所以它更快(调用一次方法是三次而不是三次),但是我想使用标准的小方法我们可以获得更好的性能,甚至更短的几十毫秒。以上三个答案我都可以接受,不知道该选哪一个。

    【讨论】:

    • +1 表示努力并展示了一种不会得到深度优化的恒定输入的方法。然而并不奇怪:如果你的方法需要 37 秒来执行,最好尽可能少调用它。
    【解决方案3】:

    如果任何方法要返回相同的值,那么使用变量绝对比方法调用更可取。

    方法调用是一项昂贵的任务。但也要考虑对象大小的成本。 它总是计算时间和计算大小之间的一个过渡。我认为它因实例而异。

    【讨论】:

    【解决方案4】:

    这取决于。如果方法每次调用都返回不同的结果(例如时间戳),并且每次都需要在 otherMethod() 中完全相同的值,则需要将其存储在局部变量中。

    另一方面,如果someMethod() 总是返回相同的值,您可以随意调用该方法。但是,如果someMethod() 进行了一些广泛的计算(根据成本 -> 运行时间或内存使用情况),如果您的应用程序逻辑允许,您最好只调用一次该方法。

    【讨论】:

      猜你喜欢
      • 2015-01-25
      • 1970-01-01
      • 2019-06-21
      • 1970-01-01
      • 1970-01-01
      • 2018-01-20
      • 2013-08-04
      • 2019-08-16
      • 1970-01-01
      相关资源
      最近更新 更多