【问题标题】:Rust optimizing out loops?Rust 优化循环?
【发布时间】:2013-05-31 21:21:44
【问题描述】:

我正在做一些非常简单的基准测试来比较 C 和 Rust 的性能。我使用了一个添加整数的函数1 + 2 + ... + n(我可以通过手动计算来验证),其中n = 10^10

Rust 中的代码如下所示:

fn main() {
  let limit: u64 = 10000000000;
  let mut buf: u64 = 0;
  for u64::range(1, limit) |i| {
    buf = buf + i;
  }
  io::println(buf.to_str());
}

C代码如下:

#include <stdio.h>
int main()
{
  unsigned long long buf = 0;
  for(unsigned long long i = 0; i < 10000000000; ++i) {
    buf = buf + i;
  }
  printf("%llu\n", buf);
  return 0;
}

我编译并运行它们:

$ rustc sum.rs -o sum_rust
$ time ./sum_rust
13106511847580896768

real        6m43.122s
user        6m42.597s
sys 0m0.076s
$ gcc -Wall -std=c99 sum.c -o sum_c
$ time ./sum_c
13106511847580896768

real        1m3.296s
user        1m3.172s
sys         0m0.024s

然后我尝试使用优化标志,同样是 C 和 Rust:

$ rustc sum.rs -o sum_rust -O
$ time ./sum_rust
13106511847580896768

real        0m0.018s
user        0m0.004s
sys         0m0.012s
$ gcc -Wall -std=c99 sum.c -o sum_c -O9
$ time ./sum_c
13106511847580896768

real        0m16.779s
user        0m16.725s
sys         0m0.008s

这些结果让我感到惊讶。我确实预计优化会产生一些效果,但优化后的 Rust 版本要快 100000 倍:)。

我尝试更改n(唯一的限制是u64,运行时间仍然几乎为零),甚至尝试了不同的问题(1^5 + 2^5 + 3^5 + ... + n^5),结果相似:使用rustc -O编译的可执行文件是比没有标志快几个数量级,也比使用gcc -O9编译的相同算法快很多倍。

所以我的问题是:发生了什么事? :) 我可以理解优化1 + 2 + .. + n = (n*n + n)/2 的编译器,但我无法想象任何编译器都可以推导出1^5 + 2^5 + 3^5 + .. + n^5 的公式。另一方面,据我所知,结果一定是通过某种方式计算出来的(而且似乎是正确的)。

哦,还有:

$ gcc --version
gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
$ rustc --version
rustc 0.6 (dba9337 2013-05-10 05:52:48 -0700)
host: i686-unknown-linux-gnu

【问题讨论】:

  • 只是一个数据点,clang 在这里将movabsq $-5340232226128654848, %rsi 用于简单的求和,将movabsq $1667352834054815744, %rsi 用于i*i*i*i*i,因此消除循环的生锈并不是那么特别(我有点惊讶gcc 没有)。

标签: optimization gcc rust


【解决方案1】:

是的,编译器确实使用1 + ... + n = n*(n+1)/2 优化来删除循环,并且对于求和变量的任何幂都有类似的技巧。例如k1 are triangular numbersk2 are pyramidal numbersk3 are squared triangular numbers等。一般情况下,甚至还有一个公式可以计算kkp for any p


您可以使用更复杂的表达式,这样编译器就没有任何技巧来删除循环。例如

fn main() {
  let limit: u64 = 1000000000;
  let mut buf: u64 = 0;
  for u64::range(1, limit) |i| {
    buf += i + i ^ (i*i);
  }
  io::println(buf.to_str());
}

#include <stdio.h>
int main()
{
  unsigned long long buf = 0;
  for(unsigned long long i = 0; i < 1000000000; ++i) {
    buf += i + i ^ (i * i);
  }
  printf("%llu\n", buf);
  return 0;
}

这给了我

real    0m0.700s
user    0m0.692s
sys     0m0.004s

real    0m0.698s
user    0m0.692s
sys     0m0.000s

分别(两个编译器都使用-O)。

【讨论】:

  • 谢谢,我低估了编译器:)。但是为什么 LLVM 会包含这么特殊的优化呢?
猜你喜欢
  • 1970-01-01
  • 2017-07-20
  • 1970-01-01
  • 2021-01-21
  • 2017-02-18
相关资源
最近更新 更多