如何在Rust中不使用asm!获取原生RDTSC指令以进行性能基准测试?
嘿,我来帮你拆解这个问题~
首先,为啥会出现这种“合并再拆分”的无意义操作呢?其实问题出在std::arch::x86_64::_rdtsc()这个内置函数的设计初衷上:它就是专门用来返回合并后的64位时间戳值的,所以它的底层实现必然会生成shl rdx,32 + or rdx,rax这些指令,把RDTSC输出的EAX(低32位)和EDX(高32位)寄存器值合并成一个u64。
当你把这个合并后的u64再拆分成高低位元组时,编译器虽然能看到你做了反向操作,但它没办法“推翻”_rdtsc()的设计逻辑——它会先严格执行这个函数的要求(合并两个寄存器的值),再执行你的拆分代码,这就导致了这种来回折腾的“ping-pong”操作。编译器不会去深挖_rdtsc()的底层指令,只会基于它对外的功能(返回合并后的u64)来做优化。
那有没有办法不用asm!就拿到原生的RDTSC指令呢?
很遗憾,在当前Rust标准库提供的工具里,确实没有完美的解决方案。所有官方提供的RDTSC相关内置函数,都是以返回合并后的u64为目标的,所以编译器必须遵守这个设计,没办法跳过合并步骤。哪怕你用元组返回、加优化提示,编译器还是会先完成合并再拆分,绕不开那些额外的指令。
不过,如果你能稍微松口,其实Rust 1.59之后asm!宏已经是稳定特性了,而且写法非常简单,能直接生成你想要的原生RDTSC指令,完全没有额外开销:
#[inline(always)] fn tick_count() -> (u32, u32) { let lo: u32; let hi: u32; unsafe { core::arch::asm!( "rdtsc", out("eax") lo, out("edx") hi, options(nomem, nostack, preserves_flags) ); } (lo, hi) }
这段代码会直接生成rdtsc指令,然后把EAX和EDX寄存器的值分别存入lo和hi变量,没有任何多余的移位或或操作,完全符合你的需求。
如果你铁了心完全不碰asm!,那目前确实没有办法绕过那些额外的指令——毕竟标准库没有提供直接返回两个寄存器原始值的内置函数,编译器只能按现有语义来生成代码。
内容来源于stack exchange

