读书笔记-《高性能计算(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

参考:必读 - Microbenchmarking fused instruction

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 优化循环

文章:高性能计算(HPC)系列之二:深入基础软件开发第二篇

[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指令

高性能计算(HPC)系列之二:深入基础软件开发第三篇

TODO

posted @ 2024-01-15 21:04  LiYanbin  阅读(6)  评论(0编辑  收藏  举报