【问题标题】:In what situations can do-while be more efficient than while?在什么情况下 do-while 可以比 while 更有效率?
【发布时间】:2013-05-02 12:15:38
【问题描述】:

while 与 do-while

while 和 do-while 在功能上是等效的当块为空时,虽然 while 看起来更自然:

do {} while (keepLooping());
while (keepLooping()) {}

带有空块的 while/do-while 的一个典型用例是使用 compareAndSet (CAS) 强制更新原子对象。例如,下面的代码将以线程安全的方式递增a

int i;
AtomicInteger a = new AtomicInteger();
while (!a.compareAndSet(i = a.get(), i + 1)) {}

上下文

java.util.concurrent 的几个部分使用do {} while (...) 惯用语进行 CAS 操作,ForkJoinPool 的 javadoc 解释说:

不寻常的do {} while (!cas...) 多次出现,这是强制更新 CAS 变量的最简单方法。

既然他们承认这是不寻常的,我想他们的意思是最好的而不是最简单的

问题

在某些情况下do {} while (!cas) 可以比while (!cas) {} 更高效,原因是什么?

【问题讨论】:

  • 也许这只是历史偏好。无论如何,为什么要使用{} ---while (condition); 是我要写的。
  • @MarkoTopolnik 同意;。我找到了this video,Doug Lea 在 57:00 左右说:“不要使用 while,使用 do-while,因为安全点”,幻灯片提到“较小的比赛窗口”。我想他指的是 GC 安全点,尽管我看不出它在这里有何不同。
  • Doug 对此非常省略 :) 我可以解释它的最佳方式不是关于 do-while 与 while,而是关于使用循环体进行赋值与将所有内容都塞进条件中。
  • @MarkoTopolnik 这也是我所理解的——但是为什么会出现麻烦呢?我想 SO 不是最好的提问地点!
  • 是的...这对 SO “没有建设性”,因为地球上只有大约三个人可以给出超出猜测的答案:)

标签: java performance while-loop do-while compare-and-swap


【解决方案1】:

所以“do while”意味着它将在 while 循环中运行代码一次。然后,它只在条件为真时运行 while 循环内的代码。

简单演示

boolean condition = false;

do{
  System.out.Println("this text displayed");
}while(condition == true);

输出“此文本显示”

正常

while(condition == true){
 System.out.Println("this text displayed");
}

输出“”

  • *由于条件为假,没有显示输出。

为什么或在哪里你会使用 do while,我没有遇到需要,所以我无法帮助你。这只是识别问题/需求,并使用你所知道的来解决它的问题。类似于乐高 - 机械类型不是“积木”。

【讨论】:

    【解决方案2】:

    在某些情况下,expect 和 update 的计算非常复杂,以至于在调用 compareAndSet 的同一行中可读。 然后你可以让它在 do 中更具可读性:

    do {
      int expect = a.get();
      int update = expect + 1;
    } while (!a.compareAndSet(expect, update));
    

    【讨论】:

    • 该问题假定为空块,因此 while 和 do-while 是严格等价的。
    • 啊哈。也许这更像是它
    • 看这个例子:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… - 我认为这不是问题。
    • 在大多数情况下,我同意 while (cas);更好。我只展示了 do {} while (!cas) 比 while (!cas) {} 更具可读性的示例。这就是为什么 do {} while 在文档中更常见的原因
    • 您的代码将无法编译,因为一旦您离开循环体,expectupdate 就会超出范围。不过,在循环之前声明这两个变量是可行的,所以你的观点仍然有效。
    【解决方案3】:

    这不是效率问题。有些情况没有 do{}while() 就无法解决。看看 java.util.Random.next(int bits)。如果您尝试对 while(){} 执行相同操作,则会出现代码重复,因为循环体必须在条件之前执行一次。

    我已经问过一个非常相似的问题:compiling loops in Java

    这段代码:

    public class Test {
    
        static int i = 0;
    
        public static void main(String[] args) {
            method1();
            method2();
        }
    
        public static void method2() {
            do{}while(++i < 5);
        }
    
        public static void method1() {
            while(++i < 5);
        }
    }
    

    编译成:

    public static void method2();
      Code:
       0:   getstatic       #4; //Field i:I
       3:   iconst_1
       4:   iadd
       5:   dup
       6:   putstatic       #4; //Field i:I
       9:   iconst_5
       10:  if_icmplt       0
       13:  return
    
    public static void method1();
      Code:
       0:   getstatic       #4; //Field i:I
       3:   iconst_1
       4:   iadd
       5:   dup
       6:   putstatic       #4; //Field i:I
       9:   iconst_5
       10:  if_icmpge       16
       13:  goto    0
       16:  return
    

    您可能会注意到 method1() 中第 13 行的附加指令。但正如我的问题中的回答所建议的那样,当由 JIT 编译成机器指令时,这没有任何区别。非常难以捉摸的性能改进。任何证明它的方法都必须使用 PrintAssembly 键运行。理论上method2更快,但实际上它们应该是相等的。

    【讨论】:

    • 你没有仔细阅读这个问题。根本没有循环体,效率问题非常低级,涉及最终的本机代码顺序。
    • 我刚刚看了看组装,没有什么让我印象深刻的。
    • 什么意思?都是一样的吗?
    • 只有两条指令出现顺序不同,其余完全一致。
    • 为什么人们认为反汇编的字节码是有益的? 1) 我们都知道 whiledo while 是什么意思。 2)你不能从字节码中推断出任何关于代码效率的东西。 JIT 编译器是进行重大优化的地方。 (即使是具有相同字节码序列的方法也可能执行不同的......由于 JIT 编译器使用来自解释器的运行时统计信息的方式。)
    猜你喜欢
    • 1970-01-01
    • 2013-12-01
    • 2013-04-10
    • 2021-08-19
    • 2013-12-26
    • 2015-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多