【问题标题】:Speed of if compared to conditionalif 与条件比较的速度
【发布时间】:2014-09-21 16:18:31
【问题描述】:

我的想法是使用条件运算符将我的一些 if 块变成单行。但是,我想知道是否会有速度差异。我进行了以下测试:

static long startTime;
static long elapsedTime;
static String s;
    
public static void main(String[] args) {
    startTime = System.nanoTime();
    s = "";
    for (int i= 0; i < 1000000000; i++) {
        if (s.equals("")) {
            s = "";
        }
    }
    
    elapsedTime = System.nanoTime() - startTime;
    
    System.out.println("Type 1 took this long: " + elapsedTime + " ns");
    
    startTime = System.nanoTime();
    s = "";
    for (int i= 0; i < 1000000000; i++) {
        s = (s.equals("") ? "" : s);
    }
    
    elapsedTime = System.nanoTime() - startTime;
    
    System.out.println("Type 2 took this long: " + elapsedTime + " ns");
}

这是我的结果:

类型 1 花了这么长时间:3293937157 ns

Type 2 花了这么长时间:2856769127 ns

我在这里做错了吗?

假设s.equals("") 一定是正确的,这是使您的代码更快的可行方法吗?

【问题讨论】:

  • 第一个似乎稍微慢,因为你正在创建添加字符串""到字符串池.在第二种情况下,它已经在字符串池中可用。所以,你节省了时间。尝试将startTime = System.nanoTime(); 放在s = ""; 之后的开头并检查。 编译器/JVM 可以更好地理解某些内容,并且取决于机器。不要依赖这样的基准。
  • 另一件“小事”:在实施基准测试时 - 您应该提供“热身”。
  • 尝试更改顺序并先执行 test2。结果:你最后运行的测试是最快的。
  • @alfasin 的第二个建议不仅仅是一件小事(我怀疑他知道 :))。你的第一个循环是从解释代码开始(例如String.equals),然后编译和优化它。第二个循环无需执行任何工作即可获得所有这些好处。像这样的微基准在 Java 中是出了名的困难。我建议你阅读上面写过的许多页面(谷歌“java microbenchmark”)。
  • 是的,你做错了几件事。第一个是过早的优化,第二个是思考如果是你的瓶颈。既然你已经用分析标记了你的问题,那么分析器告诉你什么是现实世界应用程序中最慢的代码?

标签: java if-statement benchmarking conditional-operator microbenchmark


【解决方案1】:
, is this a viable way to make your code faster?

如果您的 String s; 是非静态字段,您甚至可以使其更快。当你 referencing 它十亿倍时,静态字段比非静态字段慢

public static void main(String[] args) {

    startTime = System.nanoTime();
    String s = "";
    .
    .
}

编辑:

为什么更快??

这是由于字符串对静态字段的引用。

你可以在它的字节码中看到它

    0: ldc           #23                 // String
       2: putstatic     #25                 // Field s:Ljava/lang/String;
       5: iconst_0
       6: istore_1
       7: goto          22
      10: getstatic     #25                 // Field s:Ljava/lang/String;
      13: ldc           #23                 // String
      15: invokevirtual #27                 // Method java/lang/String.equals:(L
java/lang/Object;)Z
      18: pop
      19: iinc          1, 1
      22: iload_1
      23: ldc           #33                 // int 1000000000
      25: if_icmplt     10
      28: return

如您所见,getStaticputStatic 将被调用十亿次,它的作用是调用静态字段的引用并使用 putStatic 放置字符串的引用

getStatic - 获取一个类的静态字段值,该字段由常量池索引中的字段引用标识(index1

putStatic - 将静态字段设置为类中的值,其中该字段由常量池中的字段引用索引标识(indexbyte1

查看那些导致程序运行缓慢的位移

此外,如果您使用的是 global/member field,它将创建相同的 字节码,但它会使用 getfieldputfield 与 static 的 getStaticputStatic 相同

现在让我们看看non static field 字节码

      0: ldc           #21                 // String
       2: astore_1
       3: iconst_0
       4: istore_2
       5: goto          23
       8: aload_1
       9: ldc           #21                 // String
      11: invokevirtual #23                 // Method java/lang/String.equals:(L
java/lang/Object;)Z
      14: ifeq          20
      17: ldc           #21                 // String
      19: astore_1
      20: iinc          2, 1
      23: iload_2
      24: ldc           #29                 // int 1000000000
      26: if_icmplt     8
      29: return

如您所见,它仅使用astore_1aload_1 来保存和加载非静态字段的引用,无需额外操作。

【讨论】:

  • 您能否通过提供参考/链接来支持您的“静态字段比非静态字段慢”的说法?
  • 我喜欢你的推理,但我仍然不相信 8 次左移比 aload_1astore_1 慢。我记得读过Doug lea 尽可能将本地方法变量声明为final,因为它可以提高性能。但我从来没有遇到过像你这样的索赔!再说一次,你能用 real 基准、文献或任何其他有效的参考来支持它吗?
  • aload 和 astore 所做的是加载和存储到堆栈中,没有什么复杂的简单指令,而 getStatic 和 putStatic 正在计算、加载和存储。 “将局部方法变量声明为 final”,这是因为 final 不能再次被引用,因此它更快。我没有参考任何链接,但查看字节码就足够了。
  • 并不是说“final 不能再被引用”——它不能被修改,理论上可以实现编译器优化,但这可能就是你的意思。顺便说一句,当您写“非静态”时,您是指局部变量(方法范围)还是非静态的类成员变量?
  • @alfasin 非静态局部变量
【解决方案2】:

这对我来说确实像是过早的优化。如果您仍打算以这种方式对这两种实现进行微基准测试,我建议改用isEmpty(),因为与equals() 相比,它的底层代码更直接。我的意思是,编译器/JVM 将为您做的任何优化都不太可能被equals() 中发生的事情触发,并且更能反映一种实现相对于另一种实现的任何微小好处,假设这真的很重要。

可读性应该是您决定是使用if-else还是? :的更好规则。

【讨论】:

  • isEmpty() 快了很多
【解决方案3】:

其他答案包含有用的相关信息,但如果第一种形式比第二种形式更有效,它们都没有解决真正的问题。

此基准测试无法提供可靠的结果,因为它没有正确完成:基准测试 Java 代码中的一个重要“经验法则”是提供预热。在这种情况下,第一个循环为第二个循环提供了热身。

This answer 提供了微基准测试的附加说明以及一些有用的链接。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-11-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多