【发布时间】:2021-07-30 07:18:11
【问题描述】:
我正在对 x86 内在函数的 Rust 包装器进行简单测试:莱布尼茨系列的 PI 近似值:
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
fn main() {
let mut n: u64 = 0;
let pi4 = std::f64::consts::PI / 4.0;
unsafe {
let mut dens = _mm256_set_pd(1.0f64, -3.0f64, 5.0f64, -7.0f64);
let adder = _mm256_set_pd(8.0f64, -8.0f64, 8.0f64, -8.0f64);
let ones = _mm256_set1_pd(1.0f64);
let mut rsum = _mm256_set1_pd(0.0f64);
let mut quotients: __m256d;
loop {
quotients = _mm256_div_pd(ones, dens);
rsum = _mm256_add_pd(rsum, quotients);
dens = _mm256_add_pd(dens, adder);
n = n + 1;
let vlow = _mm256_extractf128_pd(rsum, 0);
let vhigh = _mm256_extractf128_pd(rsum, 1);
let add_partial = _mm_add_pd(vlow, vhigh);
let sum = _mm_cvtsd_f64(add_partial)
+ _mm_cvtsd_f64(_mm_unpackhi_pd(add_partial, add_partial));
if f64::abs(pi4 - sum) < 1.0e-9 {
break;
}
}
}
println!("Steps: {}", 4 * n);
}
在功能上,该程序按预期工作。我的 CPU 型号是“AMD A8-9600 RADEON R7”,并且:
$ rustc --target=x86_64-linux-kernel --print target-cpus
Available CPUs for this target:
native - Select the CPU of the current host (currently bdver4).
编译时:
$ cargo build --release
时间是:
$ time target/release/sotest
real 0m1.668s
user 0m1.667s
sys 0m0.001s
但使用“本机”目标时运行速度较慢:
$ RUSTFLAGS="-C target-cpu=native" cargo build --release
...
$ time target/release/sotest
real 0m2.783s
user 0m2.778s
sys 0m0.004s
问题是“本机”目标 CPU 有什么问题?乍一看documentation,我期待一个利用我所有 CPU 提供的扩展的二进制文件:
编译器会将其转换为目标功能列表。
即使不考虑扩展,为什么会变慢?
顺便说一句,编译选择 avx 扩展会产生很大的提升:
RUSTFLAGS="-C target-feature=+avx" cargo build --release
...
real 0m0.358s
user 0m0.354s
sys 0m0.004s
编辑:使用 Ubuntu 20.04 内核 5.4.0-72-generic。 rustc 1.51.0
【问题讨论】:
-
与减速分开,您通常希望在水平求和工作之前进行多次迭代。此外,Bulldozer 系列只有 128 位宽的 SIMD 执行单元,因此 256 位向量指令解码为 2 uop。使用两个单独的 128 位向量可以避免
_mm256_extractf128_pd(vec, 1),基本上无需额外费用。它很便宜(任何端口或 BD 和 Zen1 的单 uop),但免费更好。_mm_unpackhi_pd也不是免费的,但使用 SIMD 进行除法可能是值得的。 (事实上,您可能会遇到除法延迟的瓶颈,这可能会隐藏 hsum 的工作。) -
“事实上,你可能会遇到除法延迟的瓶颈,这可能会隐藏 hsum 的工作”......事实上,这就是正在发生的事情。测试时,我尝试每 16 次循环只添加一次,没有明显的时间变化:除法规则总时间。很遗憾知道推土机的“限制”。
-
是的,考虑到延迟瓶颈,除非您可以有用地展开另一个包含 4 个元素的向量以并行运行两个 dep 链,否则可能没有任何收获。 2x
vaddpd是分开的,最长的 loop-carried dep 仅通过vdivpd。但即使在 Excavator (bdver4) 上,FP 分频器也是部分流水线的。 8-22 周期延迟,但 8-16 周期吞吐量。 (agner.org/optimize/instruction_tables.pdf)。因此,可能还有一点空间可以使用另一个 128 位或 256 位分母向量展开,以最大限度地提高 FP 除法吞吐量。
标签: rust simd intrinsics avx