【问题标题】:Is reusing variables bad for instruction-level parallelism and OoO execution?重用变量对指令级并行性和 OoO 执行不利吗?
【发布时间】:2018-10-06 04:50:45
【问题描述】:

我正在研究处理器,引起我注意的一件事是高性能 CPU 能够execute more than one instruction during a clock cycle 甚至execute them out of order 以提高性能。所有这一切都没有编译器的任何帮助。

据我了解,处理器能够通过分析 data dependencies 来确定哪些指令可以先运行/在同一 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,因为数据将存储在两个不同的位置(寄存器?)。

对于第一个示例,这是不可能的,因为如果它在指令 1 之前运行指令 2 和 3,则分配给指令 1 的值将保存在内存中(而不是来自指令 2 的值)。

问题是

如果我重用变量(如第一个示例),是否有任何策略在 1 之前运行指令 2 和 3?

或者重用变量会阻止指令级并行性和 OoO 执行?

【问题讨论】:

  • 你需要举个具体的例子。 “处理器”不是一种编程语言。每个 ISA 都有自己的规则和指南,每种编程语言、编译器和相关的运行时也规定了一些事情。变量只是对人类的一种方便。 CPU 看不到这样的东西。
  • 另外,请记住,“没有优化”并不是“从这段代码到汇编的最愚蠢的转换”。例如,如果编译器必须将代码翻译成SSA form(比如说,因为这是编译的两个阶段之间选择的中间表示),那么就不会有变量重用,无论什么可能出现在源代码中。
  • 您可能还想阅读Register Renaming
  • @tadman 我试着举个例子。然而,在写这个问题时,我在想如果理论上有任何 CPU(它们中的任何一个)可以实施来解决这个问题的策略(比如@Damien_The_Unbeliever 指出的寄存器重命名技术)。不知道它是否适合网站的范围,虽然
  • @Damien_The_Unbeliever 是的!注册重命名绝对是我一直在寻找的东西。关于“无优化”:我的意思是编译器不会检测到这是一个错误的依赖项并使用不同的寄存器(这会是一种优化吗?)

标签: parallel-processing processor instructions


【解决方案1】:

现代微处理器是一种极其精密的设备,其复杂性已经足以让大多数人无法理解其功能的各个方面。您的编译器或运行时引入了一个额外的层,这增加了复杂性。这里只能笼统地说,因为 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 的变量。它还没有初始化,所以还不需要做任何事情。

在第一次赋值时不使用该值。默认情况下,编译器通常会将您的代码直接转换为机器指令,但是当您打开通常用于生产代码的优化时,这种假设就会消失。一个好的编译器会认识到这个值永远不会被使用,并且会忽略赋值。更好的编译器会警告您该值从未使用过。

第二个实际上分配给变量该变量稍后使用。显然,在第三次分配发生之前,第二次分配必须完成。除非这些函数是微不足道的并且最终内联,否则这里没有太多可以进行的优化。然后就是这些函数的作用了。

“超标量”处理器,或能够无序运行的处理器,其雄心壮志受到限制。它最适合使用的代码类型如下:

int a = 1;
int b = f();
int c = a * 2;
int d = a + 2;

int e = g(b);

a 的分配简单直接。 b 是一个计算值。有趣的是cd 具有相同的依赖关系,并且实际上可以并行执行。它们也不依赖于b,因此理论上只要最终状态正确,它们就可以在f() 调用之前、期间或之后运行。

单个线程可以同时执行多个操作,但是大多数处理器对它们的类​​型和数量都有限制。例如,可能会发生浮点乘法和整数加法,或者可能发生两个整数加法,但不会发生两个浮点乘法运算。这取决于CPU有哪些操作,可以操作哪些寄存器,以及编译器是如何提前安排数据的。

如果您希望优化代码并缩短纳秒时间,您需要找到一本非常好的关于您所针对的 CPU 的技术手册,并花费数小时尝试不同的方法和基准测试。

简短的回答是变量无关紧要。一切都与依赖关系、编译器以及 CPU 的功能有关。

【讨论】:

  • 感谢您的出色回答!真的帮了很多忙 :) 顺便说一句,只是想更好地了解这是如何发生的。如果我不得不降到这么低来优化代码,我想我会发疯的哈哈
猜你喜欢
  • 1970-01-01
  • 2013-07-26
  • 1970-01-01
  • 1970-01-01
  • 2020-10-03
  • 2013-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多