重用变量是否不利于指令级并行性和 OoO 执行?
Is reusing variables bad for instruction-level parallelism and OoO execution?
我正在研究处理器,引起我注意的一件事是高性能 CPU 能够 execute more than one instruction during a clock cycle and even execute them out of order 以提高性能。这一切都没有编译器的帮助。
据我所知,处理器能够通过分析 data dependencies 来确定哪些指令可以 运行 first/in 相同的 ILP 并行步骤(问题).
@edit
我会试着举个例子。想象一下这两段代码:
int myResult;
myResult = myFunc1(); // 1
myResult = myFunc2(); // 2
j = myResult + 3; // 3
-
int myFirstResult, mySecondResult;
myFirstResult = myFunc1(); // 1
mySecondResult = myFunc2(); // 2
j = mySecondResult + 3; // 3
他们都做同样的事情,区别在于第一次我重用我的变量,第二次我不重用。
我假设(如果我错了请纠正我)处理器可以在第二个示例中的指令 1 之前 运行 指令 2 和 3,因为数据将存储在两个不同的地方(注册?)。
第一个例子不可能,因为如果它运行指令 2 和指令 3 在指令 1 之前,分配给指令 1 的值将保存在内存中(而不是来自说明 2).
问题是:
如果我重用变量(如第一个示例),运行指令 2 和 3 在 1 之前是否有任何策略?
或者重用变量会阻止指令级并行性和 OoO 执行?
现代微处理器是一种极其复杂的设备,其复杂性已经足以让大多数人无法理解其功能的每个方面。您的编译器或 运行time 引入了一个附加层,这增加了复杂性。在这里只能笼统地说,因为 ARM 处理器 X 可能比 ARM 处理器 Y 处理这个问题,而且两者都不同于 Intel U 或 AMD V。
仔细查看您的代码:
int myResult;
myResult = myFunc1(); // 1
myResult = myFunc2(); // 2
j = myResult + 3; // 3
int myResult
行不一定做任何 CPU 明智的事情。它只是指示编译器 将 是一个名为 myResult
且类型为 int
的变量。尚未初始化,因此无需执行任何操作。
第一次赋值时没有使用该值。默认情况下,编译器通常会非常直接地将您的代码转换为机器指令,但是当您打开优化时(您通常对生产代码执行此操作),该假设就会消失 window。一个好的编译器会认识到这个值从未被使用过,并且会忽略赋值。更好的编译器会警告您该值从未被使用过。
第二个实际上是赋值给变量和,那个变量后面会用到。显然,在第三项任务发生之前,必须完成第二项任务。除非这些功能微不足道并且最终内联,否则这里可以进行的优化不多。然后就是这些功能的作用了。
一个 "superscalar" 处理器,或者一个能够 运行 打乱事物的处理器,在它的雄心壮志上是有限制的。它最适用的代码类型类似于以下内容:
int a = 1;
int b = f();
int c = a * 2;
int d = a + 2;
int e = g(b);
a
的赋值简单直接。 b
是一个计算值。有趣的是 c
和 d
具有相同的依赖关系,实际上可以并行执行。他们也不依赖于 b
,所以 理论上 他们可以 运行 在 f()
调用之前、期间或之后,只要结束-状态正确。
单个线程可以并发执行多个操作,但大多数处理器对它们的类型和数量都有限制。例如,可能会发生浮点乘法和整数加法,或者两个整数加法,但不会发生两个浮点乘法操作。这取决于CPU有哪些操作,可以操作哪些寄存器,以及编译器如何预先安排数据。
如果您希望优化代码并缩短纳秒级的时间,您需要找到一本关于您目标 CPU(s) 的非常好的技术手册,并花费数不清的时间尝试不同的方法和基准测试。
简短的回答是变量无关紧要。一切都与依赖关系、您的编译器以及您的 CPU 具有的功能有关。
我正在研究处理器,引起我注意的一件事是高性能 CPU 能够 execute more than one instruction during a clock cycle and even execute them out of order 以提高性能。这一切都没有编译器的帮助。
据我所知,处理器能够通过分析 data dependencies 来确定哪些指令可以 运行 first/in 相同的 ILP 并行步骤(问题).
@edit
我会试着举个例子。想象一下这两段代码:
int myResult;
myResult = myFunc1(); // 1
myResult = myFunc2(); // 2
j = myResult + 3; // 3
-
int myFirstResult, mySecondResult;
myFirstResult = myFunc1(); // 1
mySecondResult = myFunc2(); // 2
j = mySecondResult + 3; // 3
他们都做同样的事情,区别在于第一次我重用我的变量,第二次我不重用。
我假设(如果我错了请纠正我)处理器可以在第二个示例中的指令 1 之前 运行 指令 2 和 3,因为数据将存储在两个不同的地方(注册?)。
第一个例子不可能,因为如果它运行指令 2 和指令 3 在指令 1 之前,分配给指令 1 的值将保存在内存中(而不是来自说明 2).
问题是:
如果我重用变量(如第一个示例),运行指令 2 和 3 在 1 之前是否有任何策略?
或者重用变量会阻止指令级并行性和 OoO 执行?
现代微处理器是一种极其复杂的设备,其复杂性已经足以让大多数人无法理解其功能的每个方面。您的编译器或 运行time 引入了一个附加层,这增加了复杂性。在这里只能笼统地说,因为 ARM 处理器 X 可能比 ARM 处理器 Y 处理这个问题,而且两者都不同于 Intel U 或 AMD V。
仔细查看您的代码:
int myResult;
myResult = myFunc1(); // 1
myResult = myFunc2(); // 2
j = myResult + 3; // 3
int myResult
行不一定做任何 CPU 明智的事情。它只是指示编译器 将 是一个名为 myResult
且类型为 int
的变量。尚未初始化,因此无需执行任何操作。
第一次赋值时没有使用该值。默认情况下,编译器通常会非常直接地将您的代码转换为机器指令,但是当您打开优化时(您通常对生产代码执行此操作),该假设就会消失 window。一个好的编译器会认识到这个值从未被使用过,并且会忽略赋值。更好的编译器会警告您该值从未被使用过。
第二个实际上是赋值给变量和,那个变量后面会用到。显然,在第三项任务发生之前,必须完成第二项任务。除非这些功能微不足道并且最终内联,否则这里可以进行的优化不多。然后就是这些功能的作用了。
一个 "superscalar" 处理器,或者一个能够 运行 打乱事物的处理器,在它的雄心壮志上是有限制的。它最适用的代码类型类似于以下内容:
int a = 1;
int b = f();
int c = a * 2;
int d = a + 2;
int e = g(b);
a
的赋值简单直接。 b
是一个计算值。有趣的是 c
和 d
具有相同的依赖关系,实际上可以并行执行。他们也不依赖于 b
,所以 理论上 他们可以 运行 在 f()
调用之前、期间或之后,只要结束-状态正确。
单个线程可以并发执行多个操作,但大多数处理器对它们的类型和数量都有限制。例如,可能会发生浮点乘法和整数加法,或者两个整数加法,但不会发生两个浮点乘法操作。这取决于CPU有哪些操作,可以操作哪些寄存器,以及编译器如何预先安排数据。
如果您希望优化代码并缩短纳秒级的时间,您需要找到一本关于您目标 CPU(s) 的非常好的技术手册,并花费数不清的时间尝试不同的方法和基准测试。
简短的回答是变量无关紧要。一切都与依赖关系、您的编译器以及您的 CPU 具有的功能有关。