首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将什么优化技术应用于总结简单算术序列的锈蚀代码?

将什么优化技术应用于总结简单算术序列的锈蚀代码?
EN

Stack Overflow用户
提问于 2018-10-24 05:53:15
回答 3查看 510关注 0票数 2

代码太天真了:

代码语言:javascript
复制
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());
}

产出如下:

代码语言:javascript
复制
$ ./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中编写了一个等价的代码,但它运行了很长时间。我想知道是什么让铁锈密码这么快。

C代码:

代码语言:javascript
复制
#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和San深处发现的漏洞后,这两个程序几乎没有时间就打印出了相同的数字。我试图将NUM_LOOP减少一个,考虑到溢出,结果似乎是合理的。此外,使用NUM_LOOP = 1000000000,两个程序都不会溢出,并在短时间内产生正确的答案。这里使用什么优化?我知道我们可以使用像(0 + NUM_LOOP - 1) * NUM_LOOP / 2这样的简单方程来计算结果,但我不认为这种计算是由具有溢出情况的编译器完成的。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2018-10-24 12:31:34

您的锈蚀代码(没有打印和计时)编译为(论哥德波特):

代码语言:javascript
复制
movabs rax, -9223372036854775807
ret

LLVM只对整个函数进行折叠,并为您计算最终值。

让我们让上限动态(非常量)来避免这种侵略性的常数折叠:

代码语言:javascript
复制
pub fn foo(num: u64) -> u64 {
    let mut sum = 0u64;
    for i in 0..num {
        sum += i;
    }

    sum
}

这导致(哥德波特):

代码语言:javascript
复制
  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()。尽管使用了更多的抽象层(即迭代器),但编译器生成的代码与上面的完全相同。
  • 若要以锈蚀格式打印Duration,可以使用{:?}格式说明符。println!("{:.2?}", d);以最合适的单位打印持续时间,精度为2。这是打印几乎所有基准的时间的好方法。
票数 9
EN

Stack Overflow用户

发布于 2018-10-24 05:57:44

由于int永远不会像您的NUM_LOOP那么大,程序将永远循环。

代码语言:javascript
复制
const uint64_t NUM_LOOP = 18446744073709551615u;

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

如果您修复了int,编译器将在这两种情况下优化这些循环。

票数 7
EN

Stack Overflow用户

发布于 2018-10-24 05:57:58

您的代码被困在一个无限循环中。

比较i < NUM_LOOP总是返回true,因为在到达NUM_LOOP之前,int i将环绕

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52961867

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档