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;

实际上,这就是函数中的所有代码(xuint64_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 中的扩展性如此之差?

使用 OpenGL 绘图而不杀死 CPU 并且不并行化

使用 SSE 矢量化在 OpenMP 中将内部循环与残差计算并行化

在 OpenMP 中并行化嵌套循环并使用更多线程执行内部循环

如何使 numba @jit 使用所有 cpu 内核(并行化 numba @jit)

OpenMP:嵌套并行化有啥好处?