读书笔记-《高性能计算(HPC)系列之二》多流水线 循环优化
HPC1 计算CPU频率
文章 高性能计算(HPC)系列之二:深入基础软件开发第一篇
方法:执行时长/周期数 约等于 1/lscpu 查看到的频率
[1/2] 实验和实验结果
代码:
main.c:
view
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/time.h>
#include <x86intrin.h>
int work(int limit)
{
register int k = 0, index = 0, limit_r = limit;
uint64_t begin = 0, end = 0;
unsigned int ui;
struct timeval tv1, tv2;
gettimeofday(&tv1, 0);
begin = __rdtscp(&ui);
for (; index < limit_r; index++) {
k = k + 1;
}
end = __rdtscp(&ui);
gettimeofday(&tv2, 0);
printf("LOOP: %i\n", limit);
printf("TSC: %lu\n", end - begin);
printf("%ld.%ld - %ld.%ld = %ld\n", tv2.tv_sec, tv2.tv_usec, tv1.tv_sec, tv1.tv_usec, (tv2.tv_sec - tv1.tv_sec) * 1000 * 1000 + (tv2.tv_usec - tv1.tv_usec));
return k;
}
int main(int argc, char *argv[])
{
work(100000000);
return 0;
}
编译(-g 不使用编译器优化):
gcc -g -masm=intel main.c
objdump -S -M intel a.out # 查看汇编代码
for (; index < limit_r; index++) {
1200: eb 07 jmp 1209 <work+0x80>
k = k + 1;
1202: 83 c3 01 add ebx,0x1
for (; index < limit_r; index++) {
1205: 41 83 c4 01 add r12d,0x1
1209: 45 39 ec cmp r12d,r13d
120c: 7c f4 jl 1202 <work+0x79>
关闭睿频:
cat /sys/devices/system/cpu/intel_pstate/min_perf_pct
echo 100 | sudo tee /sys/devices/system/cpu/intel_pstate/min_perf_pct # 直接sudo echo 100 > /sys/devices/system/cpu/intel_pstate/min_perf_pct会提示无法写入
执行结果:
LOOP: 100000000
TSC: 72805917
1705323872.185406 - 1705323872.161889 = 23517
计算主频(执行时长/周期数):
一个指令周期时长:23517*1000/72805917 = 0.32300946089313 # 23517是微妙单位
看下对不对:lscpu 是 3.2GHz 那么一个周期耗时为 (1*1000*1000*1000)us/(3.2*1000*1000*1000)Hz = 0.3125 和计算结果接近
[2/2] 可是这个tsc不对,单独看k=k+1,由于下一条指令必须等前一条执行完,所以k=k+1是不能并行的,应该要等于循环次数
文章中给出的原因是:
高端的CPU,对于这种相依赖的加1,也能进行优化。使用宏融合,可以将多条+1,融合成一条加法指令。这样导致计算出的频率远高于实际频率。对于这种情况,可以把加1,换成加1000、加1万等值试试。或者改为“阶加“,下面有一个”阶加“的例子可以参考
改成阶加k = k + i
后依然不行,请教了吕海波吕老师之后(吕老师特好,大师级别的还能耐心给路人解答,感谢),给出了宏融合的解释:
1亿次循环,你这个用了0.75亿周期,部分“阶加”融合了
k=k+N;
k=k+N;
k=k+N;
……
这样的指令流,如果N小于某个值时,可以把多个指令融合成一条。
从你这个结果上看,这个N应该还是挺大一个值,当k=k+(1, 2, 3, 6, 11, ……)时,都有融合发生。哪N比较大时,就不融合了。所以1亿次循环,使用了0.75亿个周期
附:查看cpu微架构命令
cpuid -1 | grep \(synth\)
(synth) = Intel Core i*-10000 (Comet Lake-H/S G1) [Kaby Lake] {Skylake}, 14nm+++
附:关于instructionFusion、micro fusion、macro fusion
Please do not be confused about the difference between InstructionFusion, MicroFusion and MacroFusion. According Intel documentation:
- InstructionFusion is when multiple RISC-like assembly instructions are merged into CISC-like one assembly instruction (see example above). This is made by the compiler / asm developer.
- MicroFusion is when multiple uops from the same assembly instruction are merged into one uop. This is made by the decoding pipeline inside CPU.
- MacroFusion is when multiple uops from different assembly instructions are merged into one uop. This is made by the decoding pipeline inside CPU.
请不要混淆 InstructionFusion、MicroFusion 和 MacroFusion 之间的区别。根据 Intel 文档:
- InstructionFusion 是指将多个类似 RISC 的汇编指令合并为一个类似 CISC 的汇编指令(参见上面的示例)。这是由编译器/ asm开发人员完成的。
- MicroFusion 是指将来自同一组装指令的多个 uop 合并为一个 uop。这是由 CPU 内部的解码管道完成的。
- MacroFusion 是指将来自不同汇编指令的多个 uop 合并为一个 uop。这是由 CPU 内部的解码管道完成的。
HPC2 优化循环
[1/3] 在上一篇文章和这篇文章开头,对于阶加代码, k=k+i
指令A和i++
指令B同时卡了一下,其实是想漏了:
一开始的逻辑:k+=i -> i++ (此阶段由于要等i++的结果所以这个周期k+=i不能执行)
调整后的逻辑:k+=i、i++ -> k+=i、i++ # 第一条指令k+=i 这会就可以执行i++了;而且k+=i这种指令送到执行单元,此时已经读读取到值了,i++操作不影响k+=i执行
[2/3] 实验拆分成两条加法流水线
是否能提升性能:
另外一个问题,第一篇文章中,作者的汇编指令中有一条
movslq %r12d,%rax
不太理解为什么需要这一条,而本人测试代码编译后的汇编代码只有3条
在这章节,本人测试代码从3条增加至5条(多了k2+=j, j+=2),实验结果:
[3/3] 如果CPU IPC是4,如果分析的指令流是4,那么似乎就无法再前进了,但是这是假设cpu确实按照分析的指令流来执行,实际会大于理论分析,如果偏离理论值稍大,那可以继续优化,作者提出可能的一种原因为【由于分支跳转指令,前端译码模块,没有能足量的把指令传递到后端】,对应的优化方法为【继续增加加法流水线】,我理解为减少分支跳转指令占比:
实验结果:
HPC3 使用SIMD指令
TODO
本文来自博客园,作者:LiYanbin,转载请注明原文链接:https://www.cnblogs.com/stellar-liyanbin/p/17966340