SIMD 是否需要多核 CPU?
Does SIMD require a multi-core CPU?
实现 SIMD 需要多核 CPU 吗?
我在阅读有关 SIMD 的维基百科时发现了以下短语“多个处理元素”。那么这句话和“多核CPU”有什么区别呢?
不,每个内核通常可以执行指令集中的大多数通用操作。但是SIMD操作的"multiple processing elements"只是对不同的数据(不同的字节或字)执行一次操作。
例如,ARM Cortex-A53 microarchitecture has capability to run SIMD instructions independently of other cores, while such SIMD instruction sets as MMX, SSE and SSE2的每个内核都是在单核 CPU 上首次引入的。
是的。确实如此。但仅从营销的角度来看。没有 SIMD 指令的 uP 或 uC 将很难销售。
每个内核都有自己独立的 SIMD 执行单元。在一个内核中使用 SIMD 指令不会消耗其他内核中的执行资源。即使在同一个物理芯片上,不同的内核也是独立的,因此它们可以分别进入休眠状态以节省电量,以及将它们隔离的各种其他设计原因。
我知道的一个例外:AMD Bulldozer 有两个弱整数内核共享一个 SIMD/FPU 并共享一些缓存。他们称之为 "cluster",它基本上是超线程 (SMT) 的替代方案。参见 David Kanter's Bulldozer write-up on RealworldTech。
SIMD 和多核是正交的:你可以有没有 SIMD 的多核(也许一些没有 FPU/NEON 的 ARM 芯片),你也可以有没有多核的 SIMD。
后者的许多示例,包括最突出的早期 x86 芯片,如 Pentium-MMX 到 Pentium III / Pentium 4,它们具有 MMX / SSE1 / SSE2 但为单核 CPUs。
程序中至少存在三种不同的并行性:
Instruction-level parallelism: it's possible to overlap some of the work done by different instructions within the same single thread of execution, preserving the illusion of running every instruction one after another. Exploit it by building a pipelined CPU core, or superscalar (multiple instructions per clock), or even out-of-order execution. (See my answer on a question about that 了解详情。)
创建软件时:尽可能避免长依赖链,从而将这种并行性暴露给硬件。 (例如,将 sum += a[i++]
替换为 sum1+=a[i]; sum2+=a[i+1]; i+=2;
:使用多个累加器展开)。或者使用数组而不是链表,因为下一个要加载的地址的计算成本很低,而不是您必须等待缓存未命中的内存数据的一部分。 但大多数 ILP 已经存在于 "normal" 代码中,无需做任何特殊操作,您构建更大/更高级的硬件以找到更多它,并增加平均每时钟指令数.
Data parallelism:你需要对图像的每个像素做相同的事情,或音频文件中的每个样本。 (例如混合 2 个图像,或混合两个音频流)。 通过在每个 CPU 核心中构建并行执行单元来利用这一点 因此一条指令可以并行执行 16 个单字节加法,从而在不增加计算量的情况下提高吞吐量每个时钟需要通过 CPU 内核的指令。 这是 SIMD:单指令,多数据。
音频/视频是最著名的应用程序,其中的加速是 巨大的 因为您可以将大量字节或 16 位元素放入一个固定的-宽度矢量寄存器。
通过智能编译器自动矢量化循环或手动利用 SIMD。 SIMD 将 sum += a[i];
变成 sum[0..3] += a[i+0..3]
(每个向量有 4 个元素,就像 int
或 float
和 32 位向量一样)。
Thread/task-level parallelism:利用多核CPUs,通过手动编写多线程代码或使用OpenMP或其他自动并行化工具来暴露硬件多线程循环,或使用启动多线程的库函数进行大矩阵乘法或其他操作。
或者更简单地说,一次 运行 多个独立的程序。例如使用 make -j8
进行编译以同时保持 8 个编译进程处于运行状态。 运行 您在多台计算机集群上的工作负载,甚至分布式计算也可以利用粗粒度任务级并行性。
但是多核 CPUs 使得利用细粒度线程级并行性成为可能/高效,其中任务需要共享大量数据(如大型数组),或者通过以下方式进行低延迟通信共享内存。 (例如,用锁来保护共享数据的不同部分,或无锁编程。)
这三种并行度是正交的。
在现代 CPU 上对一个非常大的 float
数组求和:
您将为每个 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
= 非线程吞吐量的 256 倍-并行化、非矢量化、单累加器 ILP 瓶颈的天真实现,就像您从 gcc -O2
获得的简单循环一样。 clang -O3 -march=native -ffast-math
会给出 SIMD,和一些 ILP(因为 clang 知道在展开时如何使用多个累加器,通常使用 4 个,这与 gcc 不同。)
您需要 OpenMP 或其他自动并行化来利用多核。
相关: 更深入地了解用于 ILP 和 SIMD 的多个累加器,用于 FMA 循环。
实现 SIMD 需要多核 CPU 吗?
我在阅读有关 SIMD 的维基百科时发现了以下短语“多个处理元素”。那么这句话和“多核CPU”有什么区别呢?
不,每个内核通常可以执行指令集中的大多数通用操作。但是SIMD操作的"multiple processing elements"只是对不同的数据(不同的字节或字)执行一次操作。
例如,ARM Cortex-A53 microarchitecture has capability to run SIMD instructions independently of other cores, while such SIMD instruction sets as MMX, SSE and SSE2的每个内核都是在单核 CPU 上首次引入的。
是的。确实如此。但仅从营销的角度来看。没有 SIMD 指令的 uP 或 uC 将很难销售。
每个内核都有自己独立的 SIMD 执行单元。在一个内核中使用 SIMD 指令不会消耗其他内核中的执行资源。即使在同一个物理芯片上,不同的内核也是独立的,因此它们可以分别进入休眠状态以节省电量,以及将它们隔离的各种其他设计原因。
我知道的一个例外:AMD Bulldozer 有两个弱整数内核共享一个 SIMD/FPU 并共享一些缓存。他们称之为 "cluster",它基本上是超线程 (SMT) 的替代方案。参见 David Kanter's Bulldozer write-up on RealworldTech。
SIMD 和多核是正交的:你可以有没有 SIMD 的多核(也许一些没有 FPU/NEON 的 ARM 芯片),你也可以有没有多核的 SIMD。
后者的许多示例,包括最突出的早期 x86 芯片,如 Pentium-MMX 到 Pentium III / Pentium 4,它们具有 MMX / SSE1 / SSE2 但为单核 CPUs。
程序中至少存在三种不同的并行性:
Instruction-level parallelism: it's possible to overlap some of the work done by different instructions within the same single thread of execution, preserving the illusion of running every instruction one after another. Exploit it by building a pipelined CPU core, or superscalar (multiple instructions per clock), or even out-of-order execution. (See my answer on a question about that 了解详情。)
创建软件时:尽可能避免长依赖链,从而将这种并行性暴露给硬件。 (例如,将
sum += a[i++]
替换为sum1+=a[i]; sum2+=a[i+1]; i+=2;
:使用多个累加器展开)。或者使用数组而不是链表,因为下一个要加载的地址的计算成本很低,而不是您必须等待缓存未命中的内存数据的一部分。 但大多数 ILP 已经存在于 "normal" 代码中,无需做任何特殊操作,您构建更大/更高级的硬件以找到更多它,并增加平均每时钟指令数.Data parallelism:你需要对图像的每个像素做相同的事情,或音频文件中的每个样本。 (例如混合 2 个图像,或混合两个音频流)。 通过在每个 CPU 核心中构建并行执行单元来利用这一点 因此一条指令可以并行执行 16 个单字节加法,从而在不增加计算量的情况下提高吞吐量每个时钟需要通过 CPU 内核的指令。 这是 SIMD:单指令,多数据。
音频/视频是最著名的应用程序,其中的加速是 巨大的 因为您可以将大量字节或 16 位元素放入一个固定的-宽度矢量寄存器。
通过智能编译器自动矢量化循环或手动利用 SIMD。 SIMD 将
sum += a[i];
变成sum[0..3] += a[i+0..3]
(每个向量有 4 个元素,就像int
或float
和 32 位向量一样)。Thread/task-level parallelism:利用多核CPUs,通过手动编写多线程代码或使用OpenMP或其他自动并行化工具来暴露硬件多线程循环,或使用启动多线程的库函数进行大矩阵乘法或其他操作。
或者更简单地说,一次 运行 多个独立的程序。例如使用
make -j8
进行编译以同时保持 8 个编译进程处于运行状态。 运行 您在多台计算机集群上的工作负载,甚至分布式计算也可以利用粗粒度任务级并行性。但是多核 CPUs 使得利用细粒度线程级并行性成为可能/高效,其中任务需要共享大量数据(如大型数组),或者通过以下方式进行低延迟通信共享内存。 (例如,用锁来保护共享数据的不同部分,或无锁编程。)
这三种并行度是正交的。
在现代 CPU 上对一个非常大的 float
数组求和:
您将为每个 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
= 非线程吞吐量的 256 倍-并行化、非矢量化、单累加器 ILP 瓶颈的天真实现,就像您从 gcc -O2
获得的简单循环一样。 clang -O3 -march=native -ffast-math
会给出 SIMD,和一些 ILP(因为 clang 知道在展开时如何使用多个累加器,通常使用 4 个,这与 gcc 不同。)
您需要 OpenMP 或其他自动并行化来利用多核。
相关: