繁体   English   中英

哪些优化技术应用于总结简单算术序列的Rust代码?

[英]What optimization techniques are applied to Rust code that sums up a simple arithmetic sequence?

该代码是幼稚的:

use std::time;

fn main() {
    const NUM_LOOP: u64 = std::u64::MAX;
    let mut sum = 0u64;
    let now = time::Instant::now();
    for i in 0..NUM_LOOP {
        sum += i;
    }
    let d = now.elapsed();
    println!("{}", sum);
    println!("loop: {}.{:09}s", d.as_secs(), d.subsec_nanos());
}

输出为:

$ ./test.rs.out
9223372036854775809
loop: 0.000000060s
$ ./test.rs.out
9223372036854775809
loop: 0.000000052s
$ ./test.rs.out
9223372036854775809
loop: 0.000000045s
$ ./test.rs.out
9223372036854775809
loop: 0.000000041s
$ ./test.rs.out
9223372036854775809
loop: 0.000000046s
$ ./test.rs.out
9223372036854775809
loop: 0.000000047s
$ ./test.rs.out
9223372036854775809
loop: 0.000000045s

该程序几乎立即结束。 我还使用for循环在C中编写了等效的代码,但运行了很长时间。 我想知道是什么使Rust代码这么快。

C代码:

#include <stdint.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

double time_elapse(struct timespec start) {
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    return now.tv_sec - start.tv_sec +
           (now.tv_nsec - start.tv_nsec) / 1000000000.;
}

int main() {
    const uint64_t NUM_LOOP = 18446744073709551615u;
    uint64_t sum = 0;
    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);

    for (int i = 0; i < NUM_LOOP; ++i) {
        sum += i;
    }

    double t = time_elapse(now);
    printf("value of sum is: %llu\n", sum);
    printf("time elapse is: %lf sec\n", t);

    return 0;
}

Rust代码使用-O编译,而C代码使用-O3编译。 C代码运行太慢,以至于还没有停止。

修复了visibleman和Sandeep发现的错误之后,这两个程序几乎都立即打印了相同的数字。 我尝试将NUM_LOOP 1,考虑到溢出,结果似乎合理。 此外,在NUM_LOOP = 1000000000 ,两个程序都不会溢出并且不会立即产生正确的答案。 这里使用什么优化? 我知道我们可以使用简单的方程式(0 + NUM_LOOP - 1) * NUM_LOOP / 2来计算结果,但是我不认为此类计算是由编译器在发生溢出情况下完成的...

由于int永远不会比您的NUM_LOOP大,因此该程序将永远循环。

const uint64_t NUM_LOOP = 18446744073709551615u;

for (int i = 0; i < NUM_LOOP; ++i) { // Change this to an uint64_t

如果您修复了int错误,则在两种情况下编译器都会优化掉这些循环。

您的Rust代码(没有打印内容和时间)会编译为( On Godbolt ):

movabs rax, -9223372036854775807
ret

LLVM只是将整个函数折叠起来并为您计算最终值。

让我们将上限设为动态(非常数)以避免这种激进的常数折叠:

pub fn foo(num: u64) -> u64 {
    let mut sum = 0u64;
    for i in 0..num {
        sum += i;
    }

    sum
}

结果是( Godbolt ):

  test rdi, rdi            ; if num == 0
  je .LBB0_1               ; jump to .LBB0_1
  lea rax, [rdi - 1]       ; sum = num - 1
  lea rcx, [rdi - 2]       ; rcx = num - 2
  mul rcx                  ; sum = sum * rcx
  shld rdx, rax, 63        ; rdx = sum / 2
  lea rax, [rdx + rdi]     ; sum = rdx + num
  add rax, -1              ; sum -= 1
  ret
.LBB0_1:
  xor eax, eax             ; sum = 0
  ret

如您所见,优化器了解到您对从0到num所有数字求和,并用一个常量公式替换了循环: ((num - 1) * (num - 2)) / 2 + num - 1 对于上面的示例:优化器可能首先将代码优化为该常数公式,然后进行常数折叠。

补充笔记

  • 另外两个答案已经指出了您在C程序中的错误。 修复后, clang 生成完全相同的程序集 (毫不奇怪)。 但是,GCC似乎并不了解这种优化,并且会生成您所期望的程序集(循环)
  • 在Rust中,一种更简单,更惯用的方式编写代码是(0..num).sum() 尽管这样做使用了更多的抽象层(即迭代器),但编译器仍生成与上面完全相同的代码。
  • 要在Rust中打印Duration ,可以使用{:?}格式说明符。 println!("{:.2?}", d); 以最适合的单位打印持续时间,精度为2。这是打印几乎所有基准测试时间的一种好方法。

您的代码陷入了无限循环。

比较i < NUM_LOOP将始终返回true,因为int i将在达到NUM_LOOP之前NUM_LOOP

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM