java需要时间来调用方法吗?

时间:2014-12-02 11:41:57

标签: java performance methods jvm

我在java中做了一些实验,我遇到了一件令我烦恼的事情。我已经意识到在java中,当我使用方法而不是直接代码时,处理它需要更多的时间。

我有以下代码:

public static void main(String[] args) {
    long nanoSeconds = System.nanoTime();
    int i = foo();
    System.out.println(i);
    System.out.println("Elapsed Nanoseconds = " + (System.nanoTime() - nanoSeconds));
    nanoSeconds = System.nanoTime();
    int l = 10;
    i = l;
    System.out.println(i);
    System.out.println("Elapsed Nanoseconds = " + (System.nanoTime() - nanoSeconds));
}

public final static int foo() {
    int i = 10;
    return i;
}

这是一个简单的代码,分为两部分。第一个测量foo()的时间并显示foo()的返回值,第二个部分执行相同但不调用foo()。

结果如下:

10

经过的纳秒= 601582

10

经过的纳秒= 49343

所以我的问题是,是否有办法不放松这种表现?

谢谢大家。

3 个答案:

答案 0 :(得分:12)

您不会以这种方式获得任何有意义的基准。

您不会考虑JIT

编译器除了非常明显的优点外,不会在这方面进行任何优化;当它在源代码中看到一个方法调用时,即使这个方法调用总是返回相同的值,它也会生成调用该方法的字节码;当它看到一个常量时,它将生成一个ldc(加载常量)字节码指令。

BUT。

然后JIT在某个时候开始。如果它确定方法调用总是返回相同的结果,那么它将内联调用。在运行时。但这只有在执行了一定数量的代码执行后才能完成,并且如果它承认它在某些时候错过了(这是回溯),它总会有回路。

这只是一个优秀的JIT实现可以执行的优化。

您想要观看this video。简而言之:使用Oracle的JVM,优化将在一段代码至少执行10000次之后开始启动 - 用于某些代码的定义"。

答案 1 :(得分:5)

以下是您在JMH上测量的代码:

@OutputTimeUnit(TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
@OperationsPerInvocation(Measure.SIZE)
@Warmup(iterations = 10, time = 100, timeUnit=MILLISECONDS)
@Measurement(iterations = 5, time = 200, timeUnit=MILLISECONDS)
@State(Scope.Thread)
@Threads(1)
@Fork(1)
public class Measure
{
  public static final int SIZE = 1;

  @Benchmark public int call() {
    int i = foo();
    return i;
  }

  @Benchmark public int callDisableOptimization() {
    int i = fooDontInline();
    return i;
  }

  @Benchmark public int inline() {
    int i;
    int l = 10;
    i = l;
    return i;
  }

  static int foo() {
    int i = 10;
    return i;
  }

  @CompilerControl(CompilerControl.Mode.DONT_INLINE)
  static int fooDontInline() {
    int i = 10;
    return i;
  }
}

说明:

  • call对应您的第一次测量,定期致电foo();
  • inline对应于您的第二次测量,您可以在其中内联foo()的逻辑而不涉及任何调用;
  • callDisableOptimization是第一种情况的特殊转折,我们使用高级低级控制来禁用JVM的自动内联。

这些是结果:

Benchmark                              Mode  Samples  Score   Error  Units
o.s.Measure.call                       avgt        5  1,279 ± 0,114  ns/op
o.s.Measure.inline                     avgt        5  1,289 ± 0,100  ns/op
o.s.Measure.callDisableOptimization    avgt        5  3,167 ± 0,217  ns/op

注意事项:

  • 您的两段代码之间实际上没有任何区别。 JIT编译器可以轻松地为您调用该方法调用,在两种情况下最终都使用完全相同的代码;
  • 增加的第三种情况表明,即使没有JIT编译器的内联,开销也是多么微不足道:真正的方法调用会产生小于2纳秒的开销。从透视角度来看,一百万次调用只会为整个运行时增加2毫秒。

关于衡量JVM性能的方法的一些评论:

  • 您尝试衡量的内容大约需要纳秒;
  • 您将纳秒与println操作一起包含在内,至少需要微秒
  • ;
  • 你根本没有做过任何热身;
  • 您未能收集具有统计代表性的样本;
  • 您尝试报告的时间为第一次测量的6 毫秒。与实际价值相差600万;
  • 第一次测量和第二次测量之间的明显差异可归因于在第一次测量期间运行的一次性初始化代码,这是一个完全不相关的人工制品;

答案 2 :(得分:0)

注意:通过只调用一次方法,您无法准确地测量这种假设的时间。您必须至少多次这样做以获得可用的结果。

是的,由于涉及更多指令,调用方法的速度较慢。当代码在另一个函数内时,它可以简单地在那里执行。调用函数时,会创建一个新的堆栈帧,并在返回后删除。这需要一点时间 - 至少在例如JIT开始并编译所述部分之前。您应该查看JVM specifications以获取更多信息。