为什么在Rust中比在C中更新大型静态浮点数组的程序更慢?

Nan*_*iao 7 c performance rust

我写了一个简单的程序来比较Rust和C的性能.

Rust版本:

use std::time::Instant;

const STREAM_ARRAY_SIZE: usize = 10000000;
static mut A: [f64; STREAM_ARRAY_SIZE] = [1.0; STREAM_ARRAY_SIZE];

fn main() {
    let now = Instant::now();

    unsafe {
        for i in 0..STREAM_ARRAY_SIZE {
            A[i] = 2.0E0 * A[i];
        }
    }

    let duration = now.elapsed();
    println!("{}", (duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64) / 1000);
}
Run Code Online (Sandbox Code Playgroud)

在调试和发布模式下运行它:

$ ./target/debug/calc
472046 us.
$ ./target/release/calc
62860 us.
Run Code Online (Sandbox Code Playgroud)

与调试相比,发布版本具有显着的性能提升.

C版本做同样的事情并在同一台服务器上运行:

#include <sys/time.h>
#include <stdio.h>

#define STREAM_ARRAY_SIZE   10000000

static double A[STREAM_ARRAY_SIZE];
int mysecond(void)
{
        struct timeval tp;
        struct timezone tzp;
        int i;

        i = gettimeofday(&tp,&tzp);
        return (tp.tv_sec * 1000000 + tp.tv_usec);
}

int main(void)
{
    int j = 0;
    for (j = 0; j < STREAM_ARRAY_SIZE; j++)
    {
        A[j] = 1.0;
    }

    int t = mysecond();
    for (j = 0; j < STREAM_ARRAY_SIZE; j++)
    {
        A[j] = 2.0E0 * A[j];
    }
    printf("%d us.\n", mysecond() - t);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

使用-O0和编译并运行它-O2:

$ gcc test.c
$ ./a.out
41626 us.
$ gcc -O2 test.c
$ ./a.out
13499 us.
Run Code Online (Sandbox Code Playgroud)

Rust优化版仅与之相比gcc -O0,并且与之相比非常弱gcc -O2.这合理吗?如何提高Rust版本的性能?

vir*_*tor 9

Rust将循环编译为:

.LBB0_1:
    movupd  xmm0, xmmword ptr [rcx + 8*rax - 48]
    movupd  xmm1, xmmword ptr [rcx + 8*rax - 32]
    addpd   xmm0, xmm0
    addpd   xmm1, xmm1
    movupd  xmmword ptr [rcx + 8*rax - 48], xmm0
    movupd  xmmword ptr [rcx + 8*rax - 32], xmm1
    movupd  xmm0, xmmword ptr [rcx + 8*rax - 16]
    movupd  xmm1, xmmword ptr [rcx + 8*rax]
    addpd   xmm0, xmm0
    addpd   xmm1, xmm1
    movupd  xmmword ptr [rcx + 8*rax - 16], xmm0
    movupd  xmmword ptr [rcx + 8*rax], xmm1
    add rax, 8
    cmp rax, 100006
    jne .LBB0_1
Run Code Online (Sandbox Code Playgroud)

而GCC 7.1.0编译为:

L6:
    movsd   (%rbx), %xmm0
    addq    $8, %rbx
    addsd   %xmm0, %xmm0
    movsd   %xmm0, -8(%rbx)
    cmpq    %rbp, %rbx
    jne     L6
Run Code Online (Sandbox Code Playgroud)

Rust将数组放入数据部分,而C实际上(memset使用模式)写入内存.这意味着运行应用程序的操作系统可能只是映射范围并依赖虚拟内存来做正确的事情.

如果在测量之前更改代码以运行相同的循环,则运行时会显着下降.它实际上比我的机器上的C版本更快.(可能是由于该循环展开)

unsafe {
    for i in 0..STREAM_ARRAY_SIZE {
        A[i] = 2.0E0 * A[i];
    }
}

let now = Instant::now();

unsafe {
    for i in 0..STREAM_ARRAY_SIZE {
        A[i] = 2.0E0 * A[i];
    }
}

let duration = now.elapsed();
Run Code Online (Sandbox Code Playgroud)

  • 你可以在你答案的第一句话中说出生锈没有错并且基准不正确:).因此,在首先看一下你的答案之后,不会得到错误的印象. (5认同)
  • 什么生锈将数组的实际值一个接一个地放在它编译的结果二进制文件中.因此,当您启动二进制文件时,它们甚至可能无法从磁盘中完全读取.当您第一次阅读它们时,它们可能会导致需要处理的页面错误.C代替在开始时循环初始化整个数组,所以当你进入循环时,范围肯定是分配和写入的.使用我之前显示的命令编译并查看生成的文件的结尾. (2认同)