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

Rob*_*bfz 2 c arrays performance rust

我是Rust的新手,最近我一直在搞乱它.我对使用Rust中的索引访问数组的性能感到好奇.

我做了这两个程序:

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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在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);

}
Run Code Online (Sandbox Code Playgroud)

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

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

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

谢谢.

DK.*_*DK. 9

首先,我假设您编译时没有进行优化,因为我无法在没有编译调试模式的情况下重现您描述的时间,而调试模式则没有特别优化.在这种情况下,鉴于Rust正在做更多工作并且已知在调试模式下生成次优代码,因此差异并不令人惊讶.

其次,这两个计划并不相同.C代码分配1001堆数组,Rust代码没有分配任何数组.其结果是,只要你做开机优化,防锈代码运行速度比C代码.

所以现在我们需要修改C程序以不分配.鉴于这种:

#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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我用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
Run Code Online (Sandbox Code Playgroud)

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

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


Luk*_*odt 9

除了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);
}
Run Code Online (Sandbox Code Playgroud)

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));
}
Run Code Online (Sandbox Code Playgroud)

在black_box与asm(...)被用来阻止优化删除所有代码.但是,我使用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
Run Code Online (Sandbox Code Playgroud)

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

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