簡體   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