理论上循环内的分支都是没有必要的,而且应该也不是black_box阻止优化,我进行简单的求和加减也是一样的结果,看起来是hashmap的问题,但是我没看出来hashbrown的实现中哪块阻止了优化.
use std::hint::black_box;
use hashbrown::HashMap;
pub fn main() {
let mut map: HashMap<u32, u32> = hashbrown::HashMap::default();
map.insert(0, 0);
for i in 0..10000 {
if let Some(_) = map.get(&0) {
continue;
} else {
black_box(i);
}
}
}
godbolt汇编 https://rust.godbolt.org/z/vr3WGsfhP
1
共 10 条评论, 1 页
评论区
写评论还有cpp的std::unordered_map在gcc下似乎也没有优化掉循环内find。
https://rust.godbolt.org/z/3s3jvaEbj
我在本地查看了下llvm-ir。 rust版本的hashbrown::get函数是一个向量化查询,内联到main之后还是循环内进行条件判断。 猜测是get函数过于复杂导致没优化
这不一定,可以循以下路径走到 .LBB0_19:
(此时可以再沿 -> .LBB0_20 -> .LBB0_23 -> .LBB0_34 走到 .LBB0_34)
指令在前面也不一定先被执行,你可以本地用 gdb 单步一下。
循环里面计算好像压根没有执行,在前面已经直接走编译器计算好的值的分支了
我(把循环次数换为 12345 以容易辨认)看了一下,
感觉那个分支还是在循环内,你说的优化是指其他方面吗,还是我有什么地方搞错了?
这感觉不一定是llvm的问题,大佬下面两个cpp的例子我感觉是llvm和gcc自动内联的阈值问题,比如下面这个例子,clang在循环内还有函数调用(所以不被视为纯函数被优化是可以理解的),我调高llvm自动内联的参数,clang就完全可以优化了,而rust版本的hashbrown已经是全内联了,但是编译器仍然毫无作为.
clang iline-more: https://rust.godbolt.org/z/jsxnxG3z4
--
👇
TinusgragLin: > 我试了下cpp标准库的哈希表是完全可以优化这种情况的
我简单测试了一下,有了一些发现:
鉴于 hashbrown 借鉴的是 SwissTable 的实现,公平起见,我用
absl::flat_hash_map
试了一下:用 x86-64-gcc 14.2 编译,见
Let's go baby!
,不见NPYD, freeze!
。用 x86-64-clang 18.1 编译,见
Let's go baby!
,也见NPYD, freeze!
。我感觉可能是 LLVM 的问题。
我简单测试了一下,有了一些发现:
鉴于 hashbrown 借鉴的是 SwissTable 的实现,公平起见,我用
absl::flat_hash_map
试了一下:用 x86-64-gcc 14.2 编译,见
Let's go baby!
,不见NPYD, freeze!
。用 x86-64-clang 18.1 编译,见
Let's go baby!
,也见NPYD, freeze!
。我感觉可能是 LLVM 的问题。
没有问题啊,你代码写错了吧
是的,我的好奇是hashbrown实现哪块阻止了优化,我看了一下理论上应该能被优化的,我试了下cpp标准库的哈希表是完全可以优化这种情况的
--
👇
Foreverhighness: 可能是 get 实现太复杂被视作了 black_box 吧。 https://rust.godbolt.org/z/sqnPPTEoc
可能是 get 实现太复杂被视作了 black_box 吧。 https://rust.godbolt.org/z/sqnPPTEoc