performance - 循环优化。寄存器重命名如何打破依赖关系?什么是执行端口容量?

标签 performance optimization x86 cpu-architecture micro-optimization

我正在分析 Agner Fog 的 optimization_assemble 中的循环示例。我指的是12.9章。 代码是:(我简化了一下)

L1: 
    vmulpd ymm1, ymm2, [rsi+rax] 
    vaddpd ymm1, ymm1, [rdi+rax] 
    vmovupd [rdi+rax], ymm1
    add rax, 32  
    jl L1   

我有一些问题:

  1. 作者说不存在循环携带依赖。我不明白为什么会这样。 (我跳过了 add rax, 32 的情况(它确实是循环进位的,但只有一个周期))。但是,毕竟,在上一次迭代尚未完成之前,下一次迭代无法修改 ymm1 寄存器。也许寄存器重命名在这里起作用?

  2. 我们假设存在循环携带依赖。 vaddpd ymm1, ymm1, [rdi+rax] -> vmovupd [rdi+rax], ymm1

设第一个延迟为 3,第二个延迟为 7。

(其实不存在这样的依赖关系,但是我想问一个假设性的问题)

现在,如何确定总延迟。我应该添加延迟,结果会是 10 吗?我不知道。

  • 上面写着:
  • There are two 256-bit read operations, each using a read port for two consecutive clock cycles, which is indicated as 1+ in the table. Using both read ports (port 2 and 3), we will have a throughput of two 256-bit reads in two clock cycles. One of the read ports will make an address calculation for the write in the second clock cycle. The write port (port 4) is occupied for two clock cycles by the 256-bit write. The limiting factor will be the read and write operations, using the two read ports and the write port at their maximum capacity.

    端口的容量到底是多少?我如何确定它们,例如 IvyBridge(我的 CPU)。

    最佳答案

    1. 是的,寄存器重命名的全部目的是在指令写入寄存器而不依赖于旧值时打破依赖链。 mov 的目标,或者 AVX 指令的只写目标操作数,是这样的。还可以归零习语,如 xor eax,eax are recognized as independent旧值,即使它们看起来将旧值作为输入。

      另请参阅 Why does mulss take only 3 cycles on Haswell, different from Agner's instruction tables? (Unrolling FP loops with multiple accumulators)有关寄存器重命名的更详细说明,以及同时运行多个循环承载依赖链的一些性能实验。

    2. 如果不重命名,vmulpd 无法写入 ymm1,直到 vmovupd 读取其操作数 ( Write-After-Read hazard ),但它不必等待vmovupd完成。请参阅计算机体系结构教科书以了解有序管道等内容。我不确定是否有任何没有 register renaming 的乱序 CPU存在。

      更新:early OoO CPUs used scoreboarding在不重命名寄存器的情况下执行一些有限的乱序执行,但它们发现和利用指令级并行性的能力受到更多限制。

    3. IvB 上的两个负载端口均具有每个时钟 1 个 128b 负载的容量。而且每个时钟生成一次地址。

      理论上,SnB/IvB 可以维持每个时钟 2x 128b 加载和 1x 128b 存储的吞吐量,但只能使用 256b 指令。它们每个时钟只能生成两个地址,但 256b 加载或存储只需每 2 个数据传输周期进行一次地址计算。请参阅Agner Fog's microarch guide

      Haswell 在端口 7 上添加了一个专用存储 AGU,仅处理简单寻址模式,并将数据路径拓宽至 256b。单个周期最多可加载 + 存储 96 字节的峰值。 (但是一些未知的瓶颈将持续吞吐量限制为低于这个值。在 Skylake 客户端上,英特尔报告的大约 84 字节/周期,与我的测试相符。)

      (据报道,根据英特尔优化指南的最新更新,IceLake 客户端每个周期可以支持 2x64B 加载 + 1x64B 存储,或 2x32B 存储。)


    另请注意,您的 indexed addressing modes won't micro-fuse ,因此融合域微指令吞吐量也是一个问题。

    关于performance - 循环优化。寄存器重命名如何打破依赖关系?什么是执行端口容量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37105230/

    相关文章:

    java - Java 中的 if(!foo) 和 if(foo == false) 之间的性能差异是什么?

    arrays - 提高阵列访问速度

    java - 编译器优化 : Java bytecode

    x86 - 添加饱和的 32 位带符号整数内在函数?

    linux - 用户空间中 x86-64 Linux 上 CS 和 SS 寄存器的含义?

    linux - 有没有办法在没有底层操作系统的情况下驱动当今的计算机 NIC?

    java - 为什么 headless (headless) NetLogo 模拟在高端计算机集群上的运行速度比在台式机上慢?

    performance - 为什么这个简单的 Haskell 程序这么慢?

    python - 为什么使用切片复制列表[:] faster than using the obvious way?

    java - 在 Java 中优化历史字符串数组的代码