【问题标题】:Testing speed of code execution with Java/Eclipse使用 Java/Eclipse 测试代码执行速度
【发布时间】:2015-10-08 07:29:22
【问题描述】:

我想测试这两种方法中哪一种执行得更快,但是取决于我先运行哪一种,该方法似乎总是运行得更慢。我怀疑 Eclipse 正在使用某种缓存机制来加快第二种方法的执行速度。

private static int method1(int n) {
    int result = 0;
    for (int i = 0; i < n + 1; ++i) {
        result += i;
    }
    return result;
}

private static int method2(int n) {
    int result = 0;
    int i = 0;
    while (i < n + 1) {
        result += i++;
    }
    return result;
}

这是我用来测试时间差异的主要功能。

long start, end;

start = new Date().getTime();
for (int i = 0; i < 100000; ++i) {
    method1(i);
}
end = new Date().getTime();
System.out.println(end - start); // 47

start = new Date().getTime();
for (int i = 0; i < 100000; ++i) {
    method2(i);
}
end = new Date().getTime();
System.out.println(end - start); // 32

【问题讨论】:

  • 用 Java 编写微基准测试需要更多的努力。查看此主题:stackoverflow.com/questions/504103/… 了解该领域的一些见解。
  • 请记住,JVM 总是需要预热,对于一些初始执行,结果时间将不值得关注。
  • 所以听起来不推荐这种方法,对吗?

标签: java eclipse performance time benchmarking


【解决方案1】:

While 和 For 循环具有相似的性能。

也许这个类似的帖子会帮助你Java For Loop Vs While Loop Performance Difference

【讨论】:

  • 哦,是的,我应该在前面提到,我选择测试 for 和 while 循环,因为我知道执行时间应该完全相同/大致相同。我更好奇为什么两次执行时间会有很大差异。
【解决方案2】:

使用new Date().getTime() 给出挂钟时间。但这确实不是我们作为开发人员想要的(大多数时候,直到进行一些使用企业级基准测试的基准测试),因为挂钟时间受到许多后台进程的影响,所以要解决这个问题Java 提供了更复杂的 API 来测量时间。

要排除其他系统活动的影响,您需要改为测量应用程序“用户时间”。

  • “用户时间”是运行应用程序自己的代码所花费的时间。
  • “CPU 时间”是用户时间加上系统时间。这是您的应用程序使用 CPU 所花费的总时间。

以下示例演示使用ManagementFactory.getThreadMXBean() API 计算 CPU 和用户时间。

    Thread thread = new Thread(){
            public void run() {
                for (int i = 0; i < 100000; ++i) {
                    int result = 0;
                    for (int j = 0; j < i + 1; ++j) {
                        result += j;
                    }
                }
                System.out.println("FOR approach: ThreadCpuTime = " + ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime()/1000000000d);
                System.out.println("FOR approach: UserTime = " + ManagementFactory.getThreadMXBean().getCurrentThreadUserTime()/1000000000d);
            };
        };
        thread.start();

        Thread thread2 = new Thread(){
            public void run() {
                for (int i = 0; i < 100000; ++i) {
                    int result = 0;
                    int j = 0;
                    while (j < i + 1) {
                        result += j++;
                    }
                }
                System.out.println("WHILE approach: ThreadCpuTime = " + ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime()/1000000000d);
                System.out.println("WHILE approach: UserTime = " + ManagementFactory.getThreadMXBean().getCurrentThreadUserTime()/1000000000d);
            };
        };
        thread2.start();


话虽如此,我真的不确定您为什么会出现意外行为,我在 Eclipse 和 IntelliJ IDE 中都运行了您的代码,而且我的 FOR 循环方法总是比 WHILE 循环快。

可能尝试重新启动 Eclipse 并运行较少数量的后台进程,或者不运行 Eclipse 但从 Java 命令行运行测试,以便确定结果。

从下面字节码分析可以看出,WHILE 和 FOR 循环的方法产生了相同的字节码,这意味着会有相同的汇编代码,因此 CPU 执行指令所需的时间相同。

但实际上,当我们在您的 IDE 或其他方式中运行时,会受到后台进程的影响,因此会观察到不同的时间。 但在这种特殊情况下 - WHILE v/s FOR,进行字节码分析并得出 WHILE 和 FOR 循环方法将花费相同时间的结论更为合适。


FOR循环字节码:

{
  public Test2();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: bipush        10
         5: if_icmpge     21
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: iload_1
        12: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        15: iinc          1, 1
        18: goto          2
        21: return
      LineNumberTable:
        line 3: 0
        line 4: 8
        line 3: 15
        line 6: 21
      StackMapTable: number_of_entries = 2
           frame_type = 252 /* append */
             offset_delta = 2
        locals = [ int ]
           frame_type = 250 /* chop */
          offset_delta = 18

}

WHILE循环的字节码:

{
  public Test();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: bipush        10
         5: if_icmpge     21
         8: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        11: iload_1
        12: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        15: iinc          1, 1
        18: goto          2
        21: return
      LineNumberTable:
        line 3: 0
        line 4: 2
        line 5: 8
        line 6: 15
        line 8: 21
      StackMapTable: number_of_entries = 2
           frame_type = 252 /* append */
             offset_delta = 2
        locals = [ int ]
           frame_type = 18 /* same */

}

延伸阅读:

【讨论】:

  • "理论上可能有人认为这两种方法都应该产生相同的结果,但实际上它不会产生相同的结果" => 我非常怀疑它们是否不同......
  • @assylias 我运行了结果,结果发现它们的时间不同。甚至 OP 也得到了不同的结果。
  • Date API 表示时钟时间(这不是我推荐的),然后是 CPU,然后是使用 ManagementFactory.getThreadMXBean() 的用户时间,我在回答中提到了代码。
  • 您尝试过吗(可能使用与 OP 相同的代码,以便我们都在同一页面上),如果是,您的结果如何?
  • 好的,但是你允许热身吗?你确定你不赞成内联第一个测试方法吗?您是否进行了多次测试并取平均值或仅取一次测试的结果?等等……
【解决方案3】:

使用jmh,我得到以下结果(对于各种 n 值),每次方法调用的分数以纳秒为单位(更小 = 更好):

Benchmark                          (n)  Mode  Samples          Score         Error  Units
c.a.p.SO31495089.method1             1  avgt       10          2.149        0.027  ns/op
c.a.p.SO31495089.method1        100000  avgt       10      34626.763      441.915  ns/op
c.a.p.SO31495089.method1    1000000000  avgt       10  322506247.405  6774340.047  ns/op
c.a.p.SO31495089.method2             1  avgt       10          2.159        0.028  ns/op
c.a.p.SO31495089.method2        100000  avgt       10      34581.273      571.416  ns/op
c.a.p.SO31495089.method2    1000000000  avgt       10  320011679.005  4049907.844  ns/op

第二次运行:

Benchmark                          (n)  Mode  Samples          Score         Error  Units
c.a.p.SO31495089.method1             1  avgt       10          2.164        0.029  ns/op
c.a.p.SO31495089.method1        100000  avgt       10      34706.194      365.189  ns/op
c.a.p.SO31495089.method1    1000000000  avgt       10  320269697.300  1696038.683  ns/op
c.a.p.SO31495089.method2             1  avgt       10          2.160        0.040  ns/op
c.a.p.SO31495089.method2        100000  avgt       10      34627.163      325.981  ns/op
c.a.p.SO31495089.method2    1000000000  avgt       10  320698252.840  2332275.430  ns/op

底线:这两种方法的性能与预期相似(执行时间的差异小于测量误差)。

【讨论】:

  • 酷哥们,我还添加了字节码分析和更多的cmets来解决你的问题。
猜你喜欢
  • 2011-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 1970-01-01
  • 2011-09-23
相关资源
最近更新 更多