通过编写简单的程序并将其与相应的C++变体进行比较来学习Rust.

事实证明,简单循环在rust中的运行速度比在c++中慢3倍,并相应地使用rust c和g++/clang++编译.在研究了这个问题之后,它似乎与以下事实有关:与g++不同,rustc不发出avx指令.

我试着在rustc标志上增加-C target-feature=+avx个,但没有成功.

就我而言,rustc使用llvm后端,所以它应该能够发出它们,这让我想到了一个问题:在rust中使用avx被认为是不安全的,还是我缺少了一种正确的方法来强制rustc启用avx?

Below I provide program source code and other relevant information:

Rust程序和编译标志:

rustc -C opt-level=3
use std::time;

const N: i32 = 2000;

fn main() {
    println!("n: {}", N);
    let start = time::Instant::now();

    let mut count: i32 = 0;
    for a in 1..=N {
        for b in 1..a {
            for c in 1..=b {
                if a*a == b*b + c*c {
                    count += 1;
                }
            }
        }
    }

    let duration = start.elapsed();
    println!("found: {}", count);
    println!("elapsed: {:?}", duration);
}

输出:

n: 2000
found: 1981
elapsed: 1.382231558s

C++程序和编译标志:

g++ -O3
clang++ -O3
#include <iostream>
#include <chrono>

constexpr int N = 2000;
constexpr double MS_TO_SEC = 1e-3;

int main(int argc, char **argv) {
    std::cout << "n: " << N << std::endl;
    auto start = std::chrono::high_resolution_clock::now();

    int count = 0;
    for (int a = 1; a <= N; ++a) {
        for (int b = 1; b < a; ++b) {
            for (int c = 1; c <= b; ++c) {
                if (a*a == b*b + c*c) {
                    count += 1;
                }
            }
        }
    }

    auto elapsed = std::chrono::high_resolution_clock::now() - start;
    std::cout << "found: " << count << std::endl;
    std::cout << "elapsed: " 
              << double(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count())*MS_TO_SEC 
              << "s" << std::endl;
}

输出:

n: 2000
found: 1981
elapsed: 0.419s

编译器版本:

g++ 9.4.0
clang++ 10.0.0
rustc 1.64.0-nightly

CPU信息:

Architecture:                    x86_64
CPU op-mode(s):                  32-bit, 64-bit
Byte Order:                      Little Endian
Address sizes:                   39 bits physical, 48 bits virtual
CPU(s):                          8
On-line CPU(s) list:             0-7
Thread(s) per core:              2
Core(s) per socket:              4
Socket(s):                       1
NUMA node(s):                    1
Vendor ID:                       GenuineIntel
CPU family:                      6
Model:                           60
Model name:                      Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz
Stepping:                        3
CPU MHz:                         800.000
CPU max MHz:                     3500,0000
CPU min MHz:                     800,0000
BogoMIPS:                        4988.85
Virtualization:                  VT-x
L1d cache:                       128 KiB
L1i cache:                       128 KiB
L2 cache:                        1 MiB
L3 cache:                        6 MiB
NUMA node0 CPU(s):               0-7
Flags:                           fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs
                                  bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_
                                 deadline_timer aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti ssbd ibrs ibpb stibp tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 
                                 avx2 smep bmi2 erms invpcid xsaveopt dtherm ida arat pln pts md_clear flush_l1d

编辑:

找到的解决方案:

推荐答案

已知包含范围是Rust中错过优化和性能问题的原因.这适用于你的情况.

您需要将..=x的全包范围转换为各自的独占形式..(x+1).注意,只有当x小于您使用的整数的最大值时,这才可能.

use std::time;

const N: i32 = 2000;

fn main() {
    println!("n: {}", N);
    let start = time::Instant::now();

    let mut count: i32 = 0;
    for a in 1..(N+1) {
        for b in 1..a {
            for c in 1..(b+1) {
                if a*a == b*b + c*c {
                    count += 1;
                }
            }
        }
    }

    let duration = start.elapsed();
    println!("found: {}", count);
    println!("elapsed: {:?}", duration);
}

如果您看一下下面的编译器浏览器diff,就会发现这使得热循环实际上与Clang发出的热循环相同:https://godbolt.org/z/q69KWh7sx

您还需要使用-C target-feature=+avx(或avx2等)来启用SIMD,因为我认为默认情况下,rustc默认会发出可移植的二进制文件(SIMD指令不一定是可移植的).

您还可以使用-C target-cpu=native来使用CPU上可用的最大功能.

Rust相关问答推荐

使用nom将任何空白、制表符、白线等序列替换为单个空白

基于对vec值的引用从该值中删除该值

为什么单元类型(空元组)实现了`Extend`trait?

程序退出后只写入指定管道的数据

抽象RUST中的可变/不可变引用

使用Clap时如何将String作为Into Str参数传递?

无法将记录器向下转换回原始 struct

如何使用盒装枚举进行模式匹配?

将一个泛型类型转换为另一个泛型类型

为什么Rust不支持带关联常量的特征对象?

如何将实现多个特征的 struct 传递给接受这些特征为&;mut?

为什么BufReader实际上没有缓冲短寻道?

Some(v) 和 Some(&v) 有什么区别?

了解 Rust 闭包:为什么它们持续持有可变引用?

改变不实现克隆的 dioxus UseState struct

`map` 调用在这里有什么用吗?

如何断言代码不会在测试中编译?

如何在 Rust 的内置函数上实现特征?

为什么 u64::trailing_zeros() 在无分支工作时生成分支程序集?

Rust:为什么在 struct 中borrow 引用会borrow 整个 struct?