我正在研究处理器,引起我注意的一件事是,高性能CPU能够execute more than one instruction during a clock cycle甚至execute them out of order以提高性能。所有这些都没有编译器的任何帮助。
据我所知,处理器能够通过分析data dependencies来确定哪些指令可以先运行/在同一个ILP-paralell-step(问题)中执行。
@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执行吗?
答案 0 :(得分: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
的{{1}}类型的变量。它没有初始化,所以没有必要做任何事情。
在第一次分配时,不使用该值。默认情况下,编译器通常会将代码直接转换为机器指令,但是当您启用通常用于生产代码的优化时,该假设就会消失。一个好的编译器会认识到这个值从未被使用过,并且会省略赋值。一个更好的编译器会警告你,该值永远不会被使用。
第二个实际上分配给变量和该变量稍后使用。显然,在第三次任务发生之前,必须完成第二项任务。除非这些功能微不足道并最终内联,否则这里的优化并不多。那么这就是这些功能的作用。
“超标量”处理器,或能够无序运行的处理器,限制了它可以获得的雄心壮志。它最适合的代码类型类似于以下内容:
int
int a = 1;
int b = f();
int c = a * 2;
int d = a + 2;
int e = g(b);
的分配很简单直接。 a
是计算值。令人感兴趣的地方是b
和c
具有相同的依赖关系,并且实际上可以并行执行。他们也不依赖于d
所以理论上他们可以在b
调用之前,期间或之后运行,只要结束状态是正确的。
单个线程可以同时执行多个操作,但大多数处理器对它们的类型和数量有限制。例如,可能发生浮点乘法和整数加,或两个整数加,但不是两个浮点乘法运算。这取决于CPU具有哪些操作,可以操作哪些寄存器,以及编译器如何提前排列数据。
如果您希望优化代码并减少纳秒数,您需要在目标CPU上找到一本非常好的技术手册,并花费数小时尝试不同的方法和基准测试。
简短的回答是变量无关紧要。这完全取决于依赖项,编译器以及CPU的功能。