CPU 内部并行化
Posted
技术标签:
【中文标题】CPU 内部并行化【英文标题】:Internal parallelization by CPU 【发布时间】:2015-02-27 11:58:44 【问题描述】:我一直在使用 Xorshift* 随机数生成器,并且遇到了 this 对其属性的探索。引用该网站(强调我的):
xorshift64* 生成器怎么会比 xorshift1024* 生成器慢?
依赖关系。 xorshift64* 生成器的三个异或/移位必须按顺序执行,因为每个都取决于前一个的结果。在 xorshift1024* 生成器中,两个 xor/shift 是完全独立的,可以在 。我还怀疑更大的状态空间使 CPU 可以执行更积极的推测执行(实际上,xorshift128* 生成器比 xorshift1024* 生成器慢)。
CPU 语句内部的这种并行化是什么意思?我认为这意味着 CPU 将使用向量指令同时执行两个异或/移位,但我无法在编译器的汇编输出中看到这一点的证据。这是深度 CPU 流水线的事情吗?或者我应该能够看到生成的汇编器中发生了什么?
【问题讨论】:
Superscalar CPUs 有多个执行单元和较长的管道 - 减少串行依赖可以通过保持更多执行单元忙碌来实现更多并行性。因此,如果您有两个 ALU,如果让它们都忙,您可以获得两倍的吞吐量。 一个 CPU 在单个代码/线程内有多个执行端口。当指令之间没有依赖关系时,可以在同一时钟上运行多条指令。 这不是编译时产生的并行度,而是CPU在运行时看到一个块中的几条指令没有依赖关系时产生的并行度。 顺便说一句,这种 CPU 并行称为指令级并行。 @didierc 那么如何让CPU看到更多独立的指令呢?他们不是编译时间生成的吗?删除不必要的依赖不是编译器的工作吗? 【参考方案1】:是的,这是 instruction-level parallelism 的事情。
基本上,这样的 CPU 将拥有比每个单独指令所需的更多执行硬件,因此它在可用资源上“展开”一堆指令,然后将结果合并回来,这样对于程序员来说,它仍然看起来事情是按顺序发生的。
如果你擅长的话,你可以看到两条相邻的指令,它们都可以工作,但没有依赖关系。例如,它们可能仅对不重叠的寄存器集进行操作。对于这种情况,您可以猜测它们可能会并行执行,从而导致该特定代码位的每周期指令值较高。
为了更具体一点,让我们看一下您正在谈论的两段代码(另外:我的学习机会)。
这是 xorshift64* 的核心:
x ^= x >> 12; // a
x ^= x << 25; // b
x ^= x >> 27; // c
return x * 2685821657736338717LL;
实际上,这就是函数中的所有代码(x
是 uint64_t
)。很明显,每一行都在触及状态,并对其进行修改,因此每条语句都依赖于它之前的语句。相比之下,这里是 xorshift1024+:
uint64_t s0 = s[ p ];
uint64_t s1 = s[ p = ( p + 1 ) & 15 ];
s1 ^= s1 << 31; // a
s1 ^= s1 >> 11; // b
s0 ^= s0 >> 30; // c
return ( s[ p ] = s0 ^ s1 ) * 1181783497276652981LL;
这里,全局状态位于uint64_t s[16], p
变量中。鉴于此,可能不是很清楚,但至少有些暗示,带有// c
注释的行不与其前面的行共享任何状态。因此,它同时进行轮班和 XOR(即“工作”),独立于之前正在完成的类似工作。因此,超标量处理器或许能够或多或少地并行运行这两条线。
【讨论】:
我会说这是 ILP 的事情,流水线、超标量只是实现 ILP 的方法。还有其他方法,如 OoO 和 VLIW 也被广泛使用。 @user3528438 已售出!谢谢。但是 VLIW 不是假设编译器正在选择指令来实现并行性,而不是指令“看起来”是顺序的,而是在实践中变得并行?我认为那里有某种不同...... 确实 VLIW 不太适合原始问题。以上是关于CPU 内部并行化的主要内容,如果未能解决你的问题,请参考以下文章
为啥在更多 CPU/内核上的并行化在 Python 中的扩展性如此之差?
使用 SSE 矢量化在 OpenMP 中将内部循环与残差计算并行化
在 OpenMP 中并行化嵌套循环并使用更多线程执行内部循环