SIMD是否必须在多核CPU上实现?在阅读有关SIMD的Wikipedia时,我发现以下短语“多个处理元素”,那么该短语与“多核CPU”有什么区别?
不,每个内核通常可以执行指令集中的大多数常规操作。但是用于SIMD操作的“多个处理元素”仅对不同数据(不同的字节或字)执行单个操作。
例如,ARM Cortex-A53微体系结构的每个内核都具有独立于其他内核运行SIMD指令的能力,而诸如MMX,SSE和SSE2之类的SIMD指令集首先是在单核CPU上引入的。
每个内核都有自己独立的 SIMD 执行单元。在一个内核中使用 SIMD 指令不会消耗其他内核中的执行资源。即使在同一物理芯片上的独立内核也是独立的,因此它们可以单独进入休眠状态以节省功耗,以及其他各种保持它们隔离的设计原因。
我知道的一个例外:AMD Bulldozer 有两个弱整数内核,它们共享一个 SIMD/FPU 并共享一些缓存。他们称之为“集群”,它基本上是超线程 (SMT) 的替代方案。请参阅David Kanter 在 RealworldTech 上的 Bulldozer 文章。
SIMD 和多核是正交的:你可以有没有 SIMD 的多核(也许一些没有 FPU/NEON 的 ARM 芯片),你可以有没有多核的 SIMD。
后者的许多例子,包括最突出的早期 x86 芯片,如 Pentium-MMX 到 Pentium III / Pentium 4,它们具有 MMX / SSE1 / SSE2 但都是单核 CPU。
程序中至少有三种不同的并行性:
指令级并行性:可以在同一个单线程执行中重叠不同指令完成的一些工作,保留一条接一条地运行每条指令的错觉。通过构建流水线 CPU 内核或超标量(每个时钟多条指令),甚至乱序执行来利用它。(有关详细信息,请参阅我对此问题的回答。)
创建软件时:尽可能避免长依赖链,从而将这种并行性暴露给硬件。(例如替换sum += a[i++]为sum1+=a[i]; sum2+=a[i+1]; i+=2;:用多个累加器展开)。或者使用数组而不是链表,因为要加载的下一个地址的计算成本很低,而不是成为内存中数据的一部分,您必须等待缓存未命中。 但大多数 ILP 已经存在于“正常”代码中,无需做任何特殊处理,您可以构建更大/更高级的硬件以找到更多它,并增加每时钟的平均指令数。
数据并行性:您需要对图像的每个像素或音频文件中的每个样本执行相同的操作。(例如混合 2 个图像,或混合两个音频流)。 通过在每个 CPU 内核中构建并行执行单元来利用这一点,这样一条指令就可以并行执行16 个单字节加法,从而提高吞吐量,而不会增加每个时钟通过 CPU 内核所需的指令数量。 这是 SIMD:单指令,多数据。
音频/视频是这方面最知名的应用,其中的加速比块状,因为你可以适用于很多字节或16位元素的成单一固定宽度的向量寄存器。
通过使用智能编译器或手动对循环进行自动矢量化来利用 SIMD。SIMD匝sum += a[i];成sum[0..3] += a[i+0..3](按矢量4个元素,如用int或float与32位矢量)。
线程/任务级并行:利用多核 CPU,通过手动编写多线程代码暴露给硬件,或使用 OpenMP 或其他自动并行化工具来多线程循环,或使用启动多个的库函数用于大矩阵乘法或其他东西的线程。
或者更简单地通过一次运行多个单独的程序。例如 compile withmake -j8可以同时保持 8 个编译进程。通过在多台计算机集群甚至分布式计算上运行您的工作负载,也可以利用粗粒度的任务级并行性。
但是多核 CPU 可以/高效地利用细粒度线程级并行性,其中任务需要共享大量数据(如大型阵列),或者通过共享内存进行低延迟通信。(例如使用锁来保护共享数据的不同部分,或无锁编程。)
这三种并行是正交的。
float在现代 CPU 上总结一个非常大的数组:
您将为每个 CPU 内核启动一个线程,并让每个内核在共享内存中的数组块上循环。(线程级并行)。比方说,这为您提供了 4 倍的加速比。(即使由于内存瓶颈,这也可能不现实,但您可以想象其他一些不需要读取这么多内存的计算密集型任务,在 28 核 Xeon 或具有其中两个芯片的双插槽服务器上运行.. .)
每个线程的代码将使用 SIMD 在每个内核上分别对每条指令进行 4 或 8 次添加。(SIMD)。这为您提供了 4 或 8 倍的加速。(或 16 与 AVX512)
您可以使用 8 个向量累加器展开以隐藏浮点加法的延迟。(ILP)。Skylake 的vaddps指令延迟为 4 个周期,吞吐量为 0.5 个周期(即每个时钟 2 个)。所以 8 个累加器几乎不足以隐藏延迟并保持 8 个 FP 立即添加指令。
单线程标量的总吞吐量增益sum += a[i++]是所有这些加速因素的乘积:4 * 8 * 8= 非并行化、非矢量化、单累加器 ILP 瓶颈幼稚实现的吞吐量的 256 倍,就像您从gcc -O2简单循环中获得的一样. clang -O3 -march=native -ffast-math会给出 SIMD 和一些 ILP(因为 clang 在展开时知道如何使用多个累加器,通常使用 4,与 gcc 不同。)
您需要 OpenMP 或其他自动并行化来利用多个内核。
相关:为什么 mulss 在 Haswell 上只需要 3 个周期,与 Agner 的指令表不同?更深入地了解 ILP 和 SIMD 的多个累加器,用于 FMA 循环。
| 归档时间: |
|
| 查看次数: |
785 次 |
| 最近记录: |