// cpu-test.c
#include <math.h>
int main(int argc, char *argv[])
{
unsigned long long c;
unsigned long long l;
double t;
for (c = 3; c < 1000000; c++) {
t = sqrt((double) c);
for (l = 2; l <= t; l++)
if (c % l == 0)
break;
}
return 0;
}
这段代码取自 sysbench ,略有删减,原函数是:
https://github.com/akopytov/sysbench/blob/master/src/tests/cpu/sb_cpu.c 的 cpu_execute_event 函数。
使用"perf stat ./cpu-test"在 3700x 上得到的 IPC 是 0.83 ,而在 5600X 上得到的是 2.23 。
1
kgdb00 OP 使用"sysbench cpu run"得到的是同样的结果,5600x 上的性能要远高于 3700x 。
|
2
booboo 2022-04-06 15:55:39 +08:00 1
是否确保编译出来的程序是一样(汇编代码比较),再就是你用了 sqrt 是动态链接的话库是否一致,操作系统版本和编译项是否一样。
一般性能差这么大,大概率是缓存引起的。 |
3
Huelse 2022-04-06 16:00:26 +08:00
据我所知,锐龙 5000 系列是新架构,三缓利用效率对比前几代提升很大,也就是单核性能很强
你都是 long long 类型的,占用比较大,此时三缓越快越明显 |
4
askonly 2022-04-06 16:02:44 +08:00
@booboo 感谢回复,编译出的程序是一样的,都是在 fedora 35 系统上,而且用 fedora 35 的 sysbench 也是这样的差距。
|
5
kgdb00 OP @booboo 感谢回复,编译出的程序是一样的,都是在 fedora 35 系统上,而且用 fedora 35 的 sysbench 也是这样的差距。刚才不小心用小号回复了。
|
6
icyalala 2022-04-06 16:15:30 +08:00
先把 branches, branch-misses, L1-dcache-loads, L1-dcache-load-misse 都打出来看看。
这段代码没什么访存压力,无非是 sqrt 里面可能有些查表操作,warmup 后应该都在 L1 内。 排除编译差别,我感觉更可能是分支预测器表现不同,但还是要看 perf 输出结果。 |
7
jdjingdian 2022-04-06 16:25:29 +08:00
不懂性能分析,但是 3700x 是 zen2 ,5600x 是 zen3 ,5600x 单核性能高出 3700x 一大截
|
8
kgdb00 OP @icyalala
在 3700x 上运行"perf stat ./cpu-test"结果如下: ``` root@develop:~/test# perf stat ./cpu-test Performance counter stats for './cpu-test': 313.31 msec task-clock # 0.999 CPUs utilized 0 context-switches # 0.000 /sec 0 cpu-migrations # 0.000 /sec 62 page-faults # 197.886 /sec 1,334,724,216 cycles # 4.260 GHz 4,152,400 stalled-cycles-frontend # 0.31% frontend cycles idle 1,084,407,060 stalled-cycles-backend # 81.25% backend cycles idle 1,112,064,247 instructions # 0.83 insn per cycle # 0.98 stalled cycles per insn 280,732,961 branches # 896.020 M/sec 479,493 branch-misses # 0.17% of all branches 0.313649907 seconds time elapsed 0.313436000 seconds user 0.000000000 seconds sys ``` 5600x 上结果如下: ``` d@desktop:~/test$ perf stat ./cpu-test Performance counter stats for './cpu-test': 109.50 msec task-clock:u # 0.997 CPUs utilized 0 context-switches:u # 0.000 /sec 0 cpu-migrations:u # 0.000 /sec 57 page-faults:u # 520.552 /sec 497,192,700 cycles:u # 4.541 GHz 1,236,868 stalled-cycles-frontend:u # 0.25% frontend cycles idle 3,047 stalled-cycles-backend:u # 0.00% backend cycles idle 1,109,781,743 instructions:u # 2.23 insn per cycle # 0.00 stalled cycles per insn 280,305,908 branches:u # 2.560 G/sec 420,708 branch-misses:u # 0.15% of all branches 0.109816486 seconds time elapsed 0.108714000 seconds user 0.000997000 seconds sys ``` |
10
choury 2022-04-06 16:38:26 +08:00
从 stalled-cycles-backend 上看,是浮点数性能差异比较大
|
11
icyalala 2022-04-06 17:13:32 +08:00
分支预测没什么太大差距。
如果同楼上浮点数性能差异的话,那就把其他都去掉,只测一下 sqrt() 看看,还有 fp 转 int 也可以单独测一下。 看了下 agner.org 的描述,Zen3 对 FP 单元提升挺大。 |
12
kgdb00 OP |
13
BrettD 2022-04-06 17:49:41 +08:00 via iPad
把完整的 annotated assembly 贴出来?
|
14
kgdb00 OP |
15
kgdb00 OP |
16
secondwtq 2022-04-06 18:37:44 +08:00
这问题在这扯过了 https://v2ex.com/t/828141
|
17
BrettD 2022-04-06 18:38:57 +08:00
> 而且我用 perf annotate 分析 ,if (c % l == 0) 这一行的汇编指令有一条 mov %rdx,%rax 在 3700X 上占了 81%的时间,在 5600x 上也占了 54%的时间。
我觉得热点显示的 mov %rdx,%rax 这一行是在等上一条 divq 指令的 rdx 余数结果出来,然后 3700X 的整数除法运算可能比 5600X 慢,所以 > 把 if (c % l == 0) 这一行去掉,3700x 的 ipc 还反超了 5600x 。 |
18
secondwtq 2022-04-06 18:54:23 +08:00
|
19
choury 2022-04-06 22:17:41 +08:00 via Android
为什么不是编绎好的同一份二进制在两台机器上测呢,你这逻辑都不一样,测鬼呢
|
21
mayli 2022-04-06 23:19:20 +08:00 via Android
3700x 的 cycle 明显比 5600x 多,大概 3x 的样子
我感觉是某个指令上 3700x 需要的 cycle 多,可以把这里面的指令拆开分别做 microbenchmark 看看具体是哪个指令慢多少。 |
23
c0xt30a 2022-04-06 23:54:49 +08:00
OP 的系统环境硬件配置是什么样子的?还有编译选项能否分享一下?
|
25
mayli 2022-04-07 01:12:43 +08:00 via Android
@kgdb00 如果是 if 这个引发的话 那就是两个 cpu 分支预测成功率不一样 不过这种情况比较罕见,现代 cpu 预测一般都差不多,而且从 perf 看 也是的确差不多
|
26
owwlo 2022-04-07 02:07:49 +08:00 via iPhone
有可能是 vulnerability mitigation 导致的么(但是差别不应该这么大)…楼主查过 lscpu 有没有 mitigation 吗?
|
27
nlzy 2022-04-07 02:14:22 +08:00 17
看了眼源码,是用试除法计算素数。那汇编都不用细看了,只要编译器不作妖,程序的瓶颈肯定是 l <= t 和 if (c % l == 0) 这两句。和缓存、访存关系都不大了。
虽然有一个 if ,但是这个分支比较好预测:即使是静态预测,每个不同的 c 也只会预测失败一次,所以和分支也没什么关系。循环条件同理。 同一个 c 的每一轮试除,l 是循环变量,而 c 和 t 是不变的,所以上述良好的分支预测再加上投机执行,是可以掩盖掉他们的延迟的,所以程序的瓶颈大概就是 l <= t 和 c % l 这两句的吞吐了。前者是整型转浮点然后比较,后者是一个除法。 上网查资料,zen2 的 div 吞吐率倒数是 13-44 ,zen3 是 7-12 。而 cvtsi2sd 和 comisd 在 zen2 和 zen3 上都是 1 。瓶颈在前者,后者可以忽略掉,而前者在 zen2 和 zen3 的性能差距正好大约是三倍。(浮点和整数除法的执行单元应该不冲突吧) 这大概就是楼主想要的答案了。 至于 mov %rdx, %rax ,寄存器重命名阶段就已经处理掉了,不会是瓶颈。 (唉,我为什么要大半夜不睡觉去分析这种编译器优化都没开,代码也没有仔细写的程序呢) |
29
kgdb00 OP @nlzy 感谢回复,我也是大半夜不睡觉,编译器优化是故意关掉的,开了 O3 优化差距一样大。你这一大堆解释我得等有空了再验证吸收一下,另外这代码是取自 sysbench ,我也不管它有啥意义,只是想搞明白性能为啥有差别。
|
30
LeeReamond 2022-04-07 03:04:14 +08:00
@nlzy 大佬怎么判断瓶颈的,为什么上文 for 中的<不是瓶颈,而下文 for 中的<=是瓶颈呢
|
31
weyou 2022-04-07 09:04:25 +08:00 via Android
@LeeReamond 前面的 for 是整型比较,后面的 for 需要是转浮点数比较,涉及浮点运算
|
32
secondwtq 2022-04-07 09:13:51 +08:00
@weyou 是因为大多数数都不是素数,所以外层循环的几乎每个 iteration ,内层循环都要跑大约 sqrt(c) 次,外层循环占得可能不到 1%。
|
33
nlzy 2022-04-07 09:34:28 +08:00 via Android
@LeeReamond 外层循环的运行次数肯定少于内层循环。这也是没考虑 sqrt() 对性能的影响的原因,它们的运行次数都远远少于那两个表达式。
|