作者:Kevin Wang
昨天,我在文章里分析了微测评的不可靠的问题,遗留了一点技术细节没有剖析清楚。评论区@王明哲提示了我可以用VTune工具,那我今天就来接着分析一下。
前情回顾
昨天的微测评中,我分别通过在queen.rs中程序开头添加NOP指令和queen.c中多次调用测试函数以及perf分析,证明了差异是由编译结果中被测代码的layout(相关代码在内存中的地址位置)导致的噪音。
今天就利用工具进一步分析layout影响了什么。
还是从加NOP开始
写个脚本,自动分别测试在queen.rs中添加1个、2个...N个NOP,看看对测试结果的影响有没有什么规律。
于是我得结果如下:
把它绘成一个曲线图:
queen.rs测试耗时与添加的NOP个数的关系
同样的方法,我们在queen.c的main函数开头添加NOP来得到C语言版的曲线。
queen.c测试耗时与添加的NOP个数的关系
可以看到Rust版的均值533和方差177相比C版还要好一些哦!(当然这个同样也不能作为评判语言性能的标准,CPU微架构层的优化太复杂微妙,不能用特例说明问题)
C和Rust两个版本的执行时间均关于插入NOP数以16为半周期大致呈周期性变化,那么这个周期16是什么鬼?看汇编代码发现编译器会自动将循环体对齐到16byte:
加9个nop的汇编,Block 2是第一个循环体开始
加10个NOP的汇编,Block 2是编译器插入的对齐NOP,原来的Block 2往后挪了16byte变成Block 3
得到线索: 本例中,第一个循环体开头对齐到奇数个16byte会得到较好的性能。
用VTune跑跑看
昨天我们已经分析了和cache line对齐无关。Intel 提供了一个工具VTune用来分析app的性能,比perf更准确详尽。那我们VTune工具来跑跑看。取rust的快慢两个版本做对比实验,分别跑出来得到这样两个Summary报告:
跑得较快的报告
跑得较慢的报告
对比两份报告可以看到,两者的分支预测失败率都高(都还有优化空间),但差异是由红圈圈出的三项导致。涉及两个东西: DSB和MITE。
大致了解了一下这两个东西:
- intel现代CPU中会将程序的机器指令转换成更细粒度的微指令(uops),主要为了实现指令的乱序执行,MITE就是执行这个转换的引擎,就像一个编译器一样。
- 由于MITE比较费时费力,新一点的CPU又引入了DSB来缓存转换结果,类似于编译缓存,只不过这个缓存容量可能很小。
那么结合工具再来理解一下,工具中给出了一些注解:
DSB Switches
Metric Description
Intel microarchitecture code name Sandy Bridge introduces a new decoded ICache. This cache, called the DSB (Decoded Stream Buffer), stores uOps that have already been decoded, avoiding many of the penalties of the legacy decode pipeline, called the MITE (Micro-instruction Translation Engine). However, when control flows out of the region cached in the DSB, the front-end incurs a penalty as uOp issue switches from the DSB to the MITE. This metric measures this penalty.
Possible Issues
A significant portion of cycles is spent switching from the DSB to the MITE. This may happen if a hot code region is too large to fit into the DSB.
Tips
Consider changing code layout (for example, via profile-guided optimization) to help your hot regions fit into the DSB.
Front-End Bandwidth
Metric Description
This metric represents a fraction of slots during which CPU was stalled due to front-end bandwidth issues, such as inefficiencies in the instruction decoders or code restrictions for caching in the DSB (decoded uOps cache). In such cases, the front-end typically delivers a non-optimal amount of uOps to the back-end.
前面summay里的三项差异的意思我就大致理解为:
DSB Switches: 慢者从DSB拿指令的命中率较低,更多地被切换到MITE现编译了。
Front-End Bandwidth MITE: 慢者花在MITE上的时间较多,MITE较忙。
Front-End Bandwidth DSB: 慢者花在从DSB取指令的时间较多(这个应该是和第一条呼应的?)。
总结下来就是慢的时候DSB命中率低了,更多时间花在了MITE上。
为啥命中率有区别呢?由于DSB缓存的是代码块,所以,这就要看我们比较热的那些块有没有对准到DSB的框框咯。
小结
故结论依然是:这种微测评结果是错误的,差异和指令对齐相关,属于噪音,有人编译出C快,有人编译出Rust快,全靠运气看编译器把指令对齐到哪里,不能体现语言的差异。
以上分析基于i7 9700K进行,其它CPU可能不同,也可能有类似机制。关于DSB,我没找到更详细的资料,也不知道我的CPU DSB有多大,有误请轻拍。