首页 > 解决方案 > 为什么 if 语句正在提高性能无用?

问题描述

我正在用 rust 编写一个小型库作为“碰撞检测”模块。在尝试尽可能快地进行线段 - 线段相交测试时,我遇到了一个奇怪的差异,其中看似无用的代码使函数运行得更快。

我的目标是尽可能优化这种方法,并且知道为什么可执行代码会像我想象的那样显着改变性能,这将有助于实现该目标。无论是 rust 编译器中的一个怪癖还是其他什么,如果有人知识渊博能够理解这一点,这就是我到目前为止所拥有的:

两个功能:

  fn line_line(a1x: f64, a1y: f64, a2x: f64, a2y: f64, b1x: f64, b1y: f64, b2x: f64, b2y: f64) -> bool {
     let dax = a2x - a1x;
     let day = a2y - a1y;
     let dbx = b2x - b1x;
     let dby = b2y - b1y;

     let dot = dax * dby - dbx * day;
     let dd = dot * dot;

     let nd1x = a1x - b1x;
     let nd1y = a1y - b1y;
     let t = (dax * nd1y - day * nd1x) * dot;
     let u = (dbx * nd1y - dby * nd1x) * dot;
     u >= 0.0 && u <= dd && t >= 0.0 && t <= dd
  }
  
  fn line_line_if(a1x: f64, a1y: f64, a2x: f64, a2y: f64, b1x: f64, b1y: f64, b2x: f64, b2y: f64) -> bool {
     let dax = a2x - a1x;
     let day = a2y - a1y;
     let dbx = b2x - b1x;
     let dby = b2y - b1y;

     let dot = dax * dby - dbx * day;
     if dot == 0.0 { return false } // useless branch if dot isn't 0 ?
     let dd = dot * dot;

     let nd1x = a1x - b1x;
     let nd1y = a1y - b1y;
     let t = (dax * nd1y - day * nd1x) * dot;
     let u = (dbx * nd1y - dby * nd1x) * dot;
     u >= 0.0 && u <= dd && t >= 0.0 && t <= dd
  }

Criterion-rs 基准代码:

  fn criterion_benchmark(c: &mut Criterion) {
     c.bench_function("line-line", |b| b.iter(|| line_line(
        black_box(0.0), black_box(1.0), black_box(2.0), black_box(0.0), 
        black_box(1.0), black_box(0.0), black_box(2.0), black_box(4.0))));
     c.bench_function("line-line-if", |b| b.iter(|| line_line_if(
        black_box(0.0), black_box(1.0), black_box(2.0), black_box(0.0), 
        black_box(1.0), black_box(0.0), black_box(2.0), black_box(4.0))));
  }

请注意,使用这些输入,if 语句将不会评估为真。

assert_eq!(line_line_if(0.0, 1.0, 2.0, 0.0, 1.0, 0.0, 2.0, 4.0), true); // does not panic

Criterion-rs 基准测试结果:

line-line               time:   [5.6718 ns 5.7203 ns 5.7640 ns]
line-line-if            time:   [5.1215 ns 5.1791 ns 5.2312 ns]

Godbolt.org 对这两段代码的 rust 编译结果

不幸的是,我无法像我想要的那样阅读 asm。我不知道如何让 rustc 编译它而不在网站内执行整个计算的内联,但如果你能做到这一点,我希望这可能会有所帮助?

我当前的环境是: -Windows
10(最新标准)
-Ryzen 3200G cpu-
标准 Rust 安装(无夜间/测试版),使用 znver1 编译(最后我检查过)

正在使用cargo bench命令进行测试,我认为它正在进行优化,但在任何地方都没有找到对此的确认。Criterion-rs 正在做所有的基准测试。

如果您能够回答这个问题,请提前感谢您。

编辑:

将godbolt链接中的代码与-C opt-level=3 --target x86_64-pc-windows-msvc(我当前安装的rustc使用的工具链)进行比较,可以发现编译器在函数中插入了许多unpcklpd和unpckhpd指令,这可能会减慢代码速度而不是优化它。将 opt-level 参数设置为 2 会删除额外的 unpck 指令,而不会显着改变编译结果。但是,添加 [profile.release]
opt-level = 2
虽然导致重新编译,但向后性能保持不变,与opt-level = 1. 我没有target-cpu=native指定。

标签: performanceif-statementrustbenchmarking

解决方案


推荐阅读