前言: docker部署的相同的业务,Host OS也是相同的版本,但是一段代码跑在E5-2630 v4和Gold 5118上,性能却相差很多。业务在在Gold 5118上,QPS下降到了E5-2630 v4的三分之一左右,而且CPU使用率更高。 Gold 5118是Products formerly Skylake系列,E5-2630 v4是Products formerly Broadwell 系列。按理说,Skylake是更新的架构,性能应该更好才对,然而实际表现却并非如此。 分析: 1,perf 在两台机器分别执行perf,发现在5118上,有些不同的地方,libgomp中出现了热点。 先用md5sum确认两个so是否出现了差异,结果是相同的。 因为libgomp被strip过,所以没有对应的symbol,perf只能拿到热点的IP:0xfc79。 使用#objdump -D得到disassembly code,如下
IP是下一条指令,也就是说 0xfc77的pause指令,是热点的指令。 2,pause 查SDM,pause的说明如下,一般的应用场景是“spin-wait loop”中。
看起来并不能解释上述的问题。 3,pause cycles google了一下,有人提到在skylake上,pause指令的执行的cycles变多了。 写代码实际测试(https://github.com/pacepi/tool/blob/master/pause-cycles.c): #include <stdio.h> static inline unsigned long long rdtsc(void) { unsigned long low, high; asm volatile("rdtsc" : "=a" (low), "=d" (high) ); return ((low) | (high) << 32); } #define CPUPAUSELOOP "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n"\ "pause\n" void bench(int loops, unsigned long rdtsc_cycles) { int i = 0; for (i = 0; i < loops; i++) { unsigned long start, finish, elapsed; start = rdtsc(); asm( CPUPAUSELOOP CPUPAUSELOOP CPUPAUSELOOP CPUPAUSELOOP CPUPAUSELOOP :: :); finish = rdtsc(); elapsed = finish - start - rdtsc_cycles; printf("total = %ld, average = %ld\n", elapsed, elapsed / 100); } } unsigned long benchrdtsc() { unsigned long start, finish, elapsed; start = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); finish = rdtsc(); elapsed = finish - start; return elapsed / 10; } int main() { unsigned long rdtsc_cycles = benchrdtsc(); bench(10, rdtsc_cycles); return 0; } 在5118上执行的结果是120,在E5-2630 v4执行的结果是9。pause指令在5118上比2630上执行的时间超过10倍。 这里需要注意的是,测试的时候,需要先确认p-state是powersave模式还是performence模式。 #find /sys/devices/system/cpu -name scaling_governor | xargs cat 4,libgomp libgomp是gcc的一个lib,代码路径https://github.com/gcc-mirror/gcc/tree/master/libgomp
libgomp自己实现了do_spin,继续看cpu_relax的实现
可见,如果拿不到锁,就会执行count次的pause。那么,在5118上,就会执行更长的时间。因为这里是busy loop,执行的时候会被统计成进程的user time。也就是说,同样的这段代码在5118上会使用更长的时间。 而docker基本都会用cgroup限制cpu quota,在5118上,时间片更容易消耗完而被调度走。所以,QPS会有下降。 好在libgomp提供了动态配置count的方法,在启动阶段,如果在环境变量中可以正确找到“GOMP_SPINCOUNT”则使用用户配置,否则就是hard code。
一个很犀利的同事给出了这个问题的暂时解决办法:在5118上pause指令的性能大约下降了14倍,所以“GOMP_SPINCOUNT”的值就是30000000000的14分之1,大约2000000000。在启动前执行#export GOMP_SPINCOUNT=2000000000,问题缓解。 5,glibc 在glibc2.23上,
在glibc2.27上,
这里需要注意的是,尽管#define atomic_spin_nop() asm ("rep; nop"),但是在编译之后,"rep; nop"依然被gcc替换成为“pause”。 在不同版本的glibc使用pthread_spin_lock函数,会出现不同的热点。 后记: 其他的问题,在skylake上如果性能突然变得不好,热点抓到是pause指令,很可能就是这个原因导致。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有