为什么在展开的ADD循环中重新初始化寄存器,即使在循环中包含更多指令的情况下,其运行速度也更快?

我有以下代码:

#include <iostream>
#include <chrono>

#define ITERATIONS "10000"

int main()
{
    /*
    ======================================
    The first case: the MOV is outside the loop.
    ======================================
    */

    auto t1 = std::chrono::high_resolution_clock::now();

    asm("mov $100,%eax\n"
        "mov $200,%ebx\n"
        "mov $" ITERATIONS ",%ecx\n"
        "lp_test_time1:\n"
        "   add %eax,%ebx\n" // 1
        "   add %eax,%ebx\n" // 2
        "   add %eax,%ebx\n" // 3
        "   add %eax,%ebx\n" // 4
        "   add %eax,%ebx\n" // 5
        "loop lp_test_time1\n");

    auto t2 = std::chrono::high_resolution_clock::now();
    auto time = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();

    std::cout << time;

    /*
    ======================================
    The second case: the MOV is inside the loop (faster).
    ======================================
    */

    t1 = std::chrono::high_resolution_clock::now();

    asm("mov $100,%eax\n"
        "mov $" ITERATIONS ",%ecx\n"
        "lp_test_time2:\n"
        "   mov $200,%ebx\n"
        "   add %eax,%ebx\n" // 5
        "loop lp_test_time2\n");

    t2 = std::chrono::high_resolution_clock::now();
    time = std::chrono::duration_cast<std::chrono::nanoseconds>(t2 - t1).count();
    std::cout << '\n' << time << '\n';
}

第一种情况

我用

编译
gcc version 9.2.0 (GCC)
Target: x86_64-pc-linux-gnu

gcc -Wall -Wextra -pedantic -O0 -o proc proc.cpp

及其输出是

14474
5837

我也用Clang编译了它,结果相同。

那么,为什么第二种情况更快(几乎是3倍的加速)?它实际上与某些微建筑细节有关吗?如果有问题,我可以使用AMD的CPU:“ AMD A9-9410 RADeon R5,5个计算核心2C + 3G”。

wangweimayuanyuan 回答:为什么在展开的ADD循环中重新初始化寄存器,即使在循环中包含更多指令的情况下,其运行速度也更快?

循环内的

mov $200,%ebx通过ebx打破了循环承载的依赖关系链,允许无序执行在多次迭代中重叠5条add指令链

如果没有它,add指令链将阻塞add(1个周期)关键路径的延迟循环,而不是吞吐量(在挖掘机上为4个/周期,从 2 /在Steamroller上循环)。您的CPU是Excavator core

AMD,因为Bulldozer具有高效的loop指令(仅1个uop),与Intel CPU不同,loop会在每7个循环中以1次迭代成为瓶颈。 (https://agner.org/optimize/用于说明表,微体系结构指南,以及有关此答案中所有内容的更多详细信息。)

loopmov的前端(和后端执行单元)中,插槽离开add的情况下,加速速度是3倍而不是4倍。

请参阅this answer,以获取CPU如何查找和利用指令级并行性(ILP)的介绍。

有关重叠的独立dep链的一些详细信息,请参见Understanding the impact of lfence on a loop with two long dependency chains,for increasing lengths


顺便说一句,一万次迭代并不多。在那段时间,您的CPU甚至可能不会超出空闲速度。或可能在第二个循环的大部分时间跳到最大速度,但第一个没有。因此,请小心使用此类微基准测试。

此外,您的嵌入式asm是不安全的,因为您忘记在EAX,EBX和ECX上声明Clobbers。您在不告诉编译器的情况下踩了寄存器。通常,应该始终在启用优化的情况下进行编译,但是如果这样做,则代码可能会中断。

本文链接:https://www.f2er.com/3092720.html

大家都在问