在Rust vs C中迭代2D数组性能

时间:2016-03-09 03:39:17

标签: c arrays performance rust

我是Rust的新手,最近我一直在搞乱它。我很好奇在Rust中使用索引访问数组的性能与C相比。

我做了这两个程序:

fn main() {

    let mut arr: [[i32; 1000]; 1000] = [[0; 1000]; 1000];

    for t in 0..1000 {
        for i in 0..1000 {
            for j in 0..1000 {
                arr[i][j] = (i * j) as i32;
            }
        }
    }
}

并在C:

#include <stdlib.h>
#include <string.h>

#define ARRSIZE 1000

int main() {

    int ** arr = malloc(sizeof(int*) * ARRSIZE);
    int i, j, t;

    for (i = 0; i < ARRSIZE; ++i) {
        arr[i] = malloc(sizeof(int) * ARRSIZE);
        memset((void*) arr[i], 0, sizeof(int) * ARRSIZE);
    }

    for (t = 0; t < ARRSIZE; ++t) {
        for (i = 0; i < ARRSIZE; ++i) {
            for (j = 0; j < ARRSIZE; ++j) {
                arr[i][j] = i * j;
            }
        }
    }

    for (i = 0; i < ARRSIZE; ++i) {
        free(arr[i]);
    }

    free(arr);

}

我们的想法是创建一个1000x1000的2D数组,并在每次迭代中迭代每个元素1000次,进行简单的算术运算。

两者之间的性能差距很大(C版本需要大约3秒,Rust版本需要45秒)。这是正常的,还是我在Rust版本中做错了什么?

编辑:我尝试禁用边界检查并获得相同的结果。

感谢。

2 个答案:

答案 0 :(得分:9)

首先,我假设您已经编译了而没有优化,因为我无法重现您所描述的时间而无需在调试模式下编译,具体而言没有积极优化。在这种情况下,鉴于Rust正在做更多工作已知在调试模式下生成次优代码,因此差异并不令人惊讶。

其次,这两个计划并不相同。 C代码分配1001堆数组,Rust代码没有分配任何。因此,只要您执行开关优化,Rust代码就会比C代码更快运行

所以现在我们需要将C程序修改为 not allocate。鉴于此:

#define ARRSIZE 1000

int main() {
    int arr[ARRSIZE][ARRSIZE] = { { 0 } };
    int i, j, t;

    for (t = 0; t < ARRSIZE; ++t) {
        for (i = 0; i < ARRSIZE; ++i) {
            for (j = 0; j < ARRSIZE; ++j) {
                arr[i][j] = i * j;
            }
        }
    }
}

我使用gcc -O(使用GCC 4.8.4)和rustc -O(使用Rust 1.7.0)编译的结果是:

$ time ./c-2; time ./rs-1

real    0m0.335s
user    0m0.328s
sys 0m0.000s

real    0m0.002s
user    0m0.000s
sys 0m0.000s

这是如此短暂,毫无意义。 但它变得更糟。 Rust程序如此之快的原因是程序非常简单,LLVM 完全删除它。该程序没有可见的副作用,因此它只是编译成一个立即退出的空二进制文件。

没有有意义的可以从这个基准测试中收集,除了Rust生成慢速调试可执行文件(已经相当熟知)。

答案 1 :(得分:8)

the answer of DK.

之外

要以(或多或少)有意义的方式(和其他人一起玩)来测试,我修改了这样的程序:

<强>锈

#![feature(test)]
extern crate test;

fn main() {
    let mut arr: [[i32; 1000]; 1000] = [[0; 1000]; 1000];

    for _ in 0..1000 {
        for i in 0..1000 {
            for j in 0..1000 {
                arr[i][j] = (i * j) as i32;
            }
        }
    }
    test::black_box(arr);
}

C (使用堆栈,代码主要由DK提供。):

#define ARRSIZE 1000

int main() {
    int arr[ARRSIZE][ARRSIZE] = { { 0 } };
    int i, j, t;

    for (t = 0; t < ARRSIZE; ++t) {
        for (i = 0; i < ARRSIZE; ++i) {
            for (j = 0; j < ARRSIZE; ++j) {
                arr[i][j] = i * j;
            }
        }
    }
    asm ("" : : "r" (arr));
}

black_boxasm(...)用于阻止优化程序删除所有代码。但是,我使用clang代替gcc来完成此工作。所以相比之下:

$ rustc -O test.rs      |      $ clang -O2 test.c 
$ time ./test           |      $ time ./a.out 
                        |    
real    0m0.537s        |      real    0m0.546s
user    0m0.532s        |      user    0m0.544s
sys     0m0.004s        |      sys     0m0.004s

同一程序的单次执行之间的执行时间比这两个程序在运行时的差异更大。

我想说的是什么(以及DK已经说过的话):差别应该是微不足道的。两者都应该做同样的工作;只有Rust也会绑定检查。但在这种情况下,LLVM优化器可能会删除这些。只记得建立在发布模式;)