Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
For loop 对于反向迭代,for循环还是while循环,哪个更快?_For Loop_While Loop_Rust_Memmove - Fatal编程技术网

For loop 对于反向迭代,for循环还是while循环,哪个更快?

For loop 对于反向迭代,for循环还是while循环,哪个更快?,for-loop,while-loop,rust,memmove,For Loop,While Loop,Rust,Memmove,我正在尝试在Rust中实现标准的memmove函数,我想知道向下迭代哪个方法更快(其中srcdest): 或 for循环版本中的rev()是否会显著降低速度?对于小型N,这真的不重要 Rust在迭代器上是懒惰的0..n在实际请求元素之前不会导致任何计算rev()首先请求最后一个元素。据我所知,Rust计数器迭代器很聪明,不需要生成第一个N-1元素就可以得到第四个N元素。在这种特定情况下,rev方法可能更快 在一般情况下,这取决于迭代器的访问范式和访问时间;确保访问端点需要固定的时间,并且不会产生

我正在尝试在Rust中实现标准的
memmove
函数,我想知道向下迭代哪个方法更快(其中
src
dest):


for
循环版本中的
rev()
是否会显著降低速度?

对于小型
N
,这真的不重要

Rust在迭代器上是懒惰的<代码>0..n在实际请求元素之前不会导致任何计算
rev()
首先请求最后一个元素。据我所知,Rust计数器迭代器很聪明,不需要生成第一个
N-1
元素就可以得到第四个
N
元素。在这种特定情况下,
rev
方法可能更快

在一般情况下,这取决于迭代器的访问范式和访问时间;确保访问端点需要固定的时间,并且不会产生任何影响

与所有基准问题一样,这取决于具体情况。自己测试你的
N


过早的优化也是有害的,所以如果你的
N
很小,并且你的循环不经常进行。。。别担心。

TL;DR:使用
进行
循环


两者的速度应该相同。我们可以简单地检查编译器剥离
for
循环中涉及的抽象层的能力:

#[inline(never)]
fn blackhole() {}

#[inline(never)]
fn with_for(n: usize) {
    for i in (0..n).rev() { blackhole(); }
}

#[inline(never)]
fn with_while(n: usize) {
    let mut i = n;
    while i > 0 {
        blackhole();
        i -= 1;
    }
}
这将生成此LLVM IR:

; Function Attrs: noinline nounwind readnone uwtable
define internal void @_ZN8with_for20h645c385965fcce1fhaaE(i64) unnamed_addr #0 {
entry-block:
  ret void
}

; Function Attrs: noinline nounwind readnone uwtable
define internal void @_ZN10with_while20hc09c3331764a9434yaaE(i64) unnamed_addr #0 {
entry-block:
  ret void
}
即使您不熟悉LLVM,很明显,这两个函数都编译到了相同的IR(因此显然编译到了相同的程序集)

由于它们的性能是相同的,因此对于
循环,应该选择更明确的
,并在迭代不规则的情况下保留
while
循环


编辑:解决starblue对不适合的担忧

汇编如下:

; Function Attrs: noinline nounwind uwtable
define internal void @_ZN8with_for20h7cf06f33e247fa35maaE(i32) unnamed_addr #1 {
entry-block:
  %1 = icmp sgt i32 %0, 0
  br i1 %1, label %match_case.preheader, label %clean_ast_95_

match_case.preheader:                             ; preds = %entry-block
  br label %match_case

match_case:                                       ; preds = %match_case.preheader, %match_case
  %.in = phi i32 [ %2, %match_case ], [ %0, %match_case.preheader ]
  %2 = add i32 %.in, -1
  %3 = tail call i32 @blackhole(i32 %2)
  %4 = icmp sgt i32 %2, 0
  br i1 %4, label %match_case, label %clean_ast_95_.loopexit

clean_ast_95_.loopexit:                           ; preds = %match_case
  br label %clean_ast_95_

clean_ast_95_:                                    ; preds = %clean_ast_95_.loopexit, %entry-block
  ret void
}

; Function Attrs: noinline nounwind uwtable
define internal void @_ZN10with_while20hee8edd624cfe9293IaaE(i32) unnamed_addr #1 {
entry-block:
  %1 = icmp sgt i32 %0, 0
  br i1 %1, label %while_body.preheader, label %while_exit

while_body.preheader:                             ; preds = %entry-block
  br label %while_body

while_exit.loopexit:                              ; preds = %while_body
  br label %while_exit

while_exit:                                       ; preds = %while_exit.loopexit, %entry-block
  ret void

while_body:                                       ; preds = %while_body.preheader, %while_body
  %i.05 = phi i32 [ %3, %while_body ], [ %0, %while_body.preheader ]
  %2 = tail call i32 @blackhole(i32 %i.05)
  %3 = add nsw i32 %i.05, -1
  %4 = icmp sgt i32 %i.05, 1
  br i1 %4, label %while_body, label %while_exit.loopexit
}
核心环路包括:

; -- for loop
match_case:                                       ; preds = %match_case.preheader, %match_case
  %.in = phi i32 [ %2, %match_case ], [ %0, %match_case.preheader ]
  %2 = add i32 %.in, -1
  %3 = tail call i32 @blackhole(i32 %2)
  %4 = icmp sgt i32 %2, 0
  br i1 %4, label %match_case, label %clean_ast_95_.loopexit

; -- while loop
while_body:                                       ; preds = %while_body.preheader, %while_body
  %i.05 = phi i32 [ %3, %while_body ], [ %0, %while_body.preheader ]
  %2 = tail call i32 @blackhole(i32 %i.05)
  %3 = add nsw i32 %i.05, -1
  %4 = icmp sgt i32 %i.05, 1
  br i1 %4, label %while_body, label %while_exit.loopexit
唯一的区别是:

  • 用于在调用黑洞之前递减,而在调用黑洞之后递减
  • for与0进行比较,而与1进行比较

否则,它就是相同的核心循环。

简言之:它们(几乎)同样快——使用
进行
循环


较长版本

第一:
rev()
仅适用于实现的迭代器,它提供了一个
next\u back()
方法。该方法预计在
o(n)
(次线性时间)中运行,通常甚至
o(1)
(恒定时间)。事实上,通过观察,我们可以看到它在恒定的时间内运行

现在我们知道这两个版本都有渐近相同的运行时。如果是这种情况,您通常应该停止考虑它,使用更惯用的解决方案(在本例中是
for
)。过早考虑优化通常会降低编程效率,因为性能只在您编写的所有代码中占很小的比例

但是,由于您正在实现
memmove
,性能实际上可能对您很重要。因此,让我们来看看最终的ASM。我使用了以下代码:

#![feature(start)]
#![feature(test)]

extern crate test;

#[inline(never)]
#[no_mangle]
fn with_for(n: usize) {
    for i in (0..n).rev() { 
        test::black_box(i); 
    }
}

#[inline(never)]
#[no_mangle]
fn with_while(n: usize) {
    let mut i = n;
    while i > 0 {
        test::black_box(i);
        i -= 1;
    }
}

#[start]
fn main(_: isize, vargs: *const *const u8) -> isize {
    let random_enough_value = unsafe {
        **vargs as usize
    };

    with_for(random_enough_value);
    with_while(random_enough_value);
    0
}
()

#[no_mangle]
是为了提高生成的ASM的可读性。
#inline(never)
random\u-ough\u值
以及
黑盒
用于防止LLVM优化我们不希望优化的东西。经过一些清理后,生成的ASM(在发布模式下!)如下所示:

with_for:                       |   with_while:
    testq   %rdi, %rdi          |       testq   %rdi, %rdi
    je  .LBB0_3                 |       je  .LBB1_3
    decq    %rdi                |   
    leaq    -8(%rsp), %rax      |       leaq    -8(%rsp), %rax
.LBB0_2:                        |   .LBB1_2:
    movq    %rdi, -8(%rsp)      |       movq    %rdi, -8(%rsp)
    decq    %rdi                |       decq    %rdi
    cmpq    $-1, %rdi           |       
    jne .LBB0_2                 |       jne .LBB1_2
.LBB0_3:                        |   .LBB1_3:
    retq                        |       retq
唯一的区别是
with_while
少了两条指令,因为它倒计时到0而不是-1,就像
with_for
那样

结论:如果你能判断渐进运行时是最优的,你可能根本不应该考虑优化。现代的优化器足够聪明,可以将高级结构编译成非常完美的ASM。通常,不管怎样,数据布局和由此产生的缓存效率比最少的指令数要重要得多


如果您确实需要考虑优化,请查看ASM(或LLVM IR)。在这种情况下,
循环实际上要慢一点(更多指令,与-1而不是0相比)。但是Rust程序员应该关心的情况可能很少。

注意,只有在迭代器实现时才可用,通常只有在访问“next-last”元素为
O(1)时才实现
。我发现由于某种原因,该代码的
for
循环版本会奇怪地失败,出现页面错误和一般保护错误(我对低级开发非常陌生)。因为这个原因,现在我将使用
while
循环版本;问题可能出在其他地方。这是一个糟糕的例子,因为编译器注意到两个函数都不做任何事情。使用一些非平凡的计算(可能计算并返回总和)会更有趣。@starblue:事实上,编译器注意到它什么也不做是(IMHO)一个完美的例子=>它正好演示了我想要的,编译器可以剥离
(0..n)
迭代器中涉及的抽象层,它的反转,在
for
循环中对其反向形式进行迭代。抽象(例如
for
循环)的风险总是编译器无法优化它们;这个例子证明了情况并非如此。@starblue:给你,使用一个LLVM无法内联的更好的黑洞。当然,结果是一样的,因为为了优化某些东西,LLVM首先必须将其简化为裸组件。
; -- for loop
match_case:                                       ; preds = %match_case.preheader, %match_case
  %.in = phi i32 [ %2, %match_case ], [ %0, %match_case.preheader ]
  %2 = add i32 %.in, -1
  %3 = tail call i32 @blackhole(i32 %2)
  %4 = icmp sgt i32 %2, 0
  br i1 %4, label %match_case, label %clean_ast_95_.loopexit

; -- while loop
while_body:                                       ; preds = %while_body.preheader, %while_body
  %i.05 = phi i32 [ %3, %while_body ], [ %0, %while_body.preheader ]
  %2 = tail call i32 @blackhole(i32 %i.05)
  %3 = add nsw i32 %i.05, -1
  %4 = icmp sgt i32 %i.05, 1
  br i1 %4, label %while_body, label %while_exit.loopexit
#![feature(start)]
#![feature(test)]

extern crate test;

#[inline(never)]
#[no_mangle]
fn with_for(n: usize) {
    for i in (0..n).rev() { 
        test::black_box(i); 
    }
}

#[inline(never)]
#[no_mangle]
fn with_while(n: usize) {
    let mut i = n;
    while i > 0 {
        test::black_box(i);
        i -= 1;
    }
}

#[start]
fn main(_: isize, vargs: *const *const u8) -> isize {
    let random_enough_value = unsafe {
        **vargs as usize
    };

    with_for(random_enough_value);
    with_while(random_enough_value);
    0
}
with_for:                       |   with_while:
    testq   %rdi, %rdi          |       testq   %rdi, %rdi
    je  .LBB0_3                 |       je  .LBB1_3
    decq    %rdi                |   
    leaq    -8(%rsp), %rax      |       leaq    -8(%rsp), %rax
.LBB0_2:                        |   .LBB1_2:
    movq    %rdi, -8(%rsp)      |       movq    %rdi, -8(%rsp)
    decq    %rdi                |       decq    %rdi
    cmpq    $-1, %rdi           |       
    jne .LBB0_2                 |       jne .LBB1_2
.LBB0_3:                        |   .LBB1_3:
    retq                        |       retq