[英]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
。 對於上面的示例:優化器可能首先將代碼優化為該常數公式,然后進行常數折疊。
clang
生成完全相同的程序集 (毫不奇怪)。 但是,GCC似乎並不了解這種優化,並且會生成您所期望的程序集(循環) 。 (0..num).sum()
。 盡管這樣做使用了更多的抽象層(即迭代器),但編譯器仍生成與上面完全相同的代碼。 Duration
,可以使用{:?}
格式說明符。 println!("{:.2?}", d);
以最適合的單位打印持續時間,精度為2。這是打印幾乎所有基准測試時間的一種好方法。 您的代碼陷入了無限循環。
比較i < NUM_LOOP
將始終返回true,因為int i
將在達到NUM_LOOP
之前NUM_LOOP
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.