【发布时间】:2011-10-07 03:37:37
【问题描述】:
GPGPU 编程是否只允许执行 SIMD 指令? 如果是这样,那么重新编写一个具有 设计为在通用 CPU 上运行以在 GPU 上运行?还有一个 可以转换为 SIMD 架构的算法模式?
【问题讨论】:
标签: gpgpu simd cpu-architecture
GPGPU 编程是否只允许执行 SIMD 指令? 如果是这样,那么重新编写一个具有 设计为在通用 CPU 上运行以在 GPU 上运行?还有一个 可以转换为 SIMD 架构的算法模式?
【问题讨论】:
标签: gpgpu simd cpu-architecture
嗯,GPGPU 只支持 SIMD 执行并不完全准确。许多 GPU 都有一些非 SIMD 组件。但总的来说,要充分利用 GPU,您需要运行 SIMD 代码。
但是,您不一定要编写 SIMD 指令。 IE。 GPU SIMD 不与 CPU SIMD 相同 - 即与编写代码以利用 x86 SSE(流 SIMD 扩展)等不同。事实上,作为将 CPU SIMD 带到您(我曾大量参与英特尔 MMX,这是最早的此类项目之一,并且一直遵循 FP SIMD 的演变)我经常觉得有义务纠正那些说英特尔之类的 CPU 具有 SIMD 指令的人。我更喜欢将它们视为压缩向量指令,尽管我勉强称它们为 SIMD 压缩向量指令集只是因为每个人都误用了这个名称。我还强调,诸如 MMX 和 SSE 之类的 CPU SIMD 指令集可能具有 SIMD 压缩向量执行单元——整数和浮点 ALU 等——但它们没有 SIMD 控制流,而且它们通常没有 SIMD 内存访问(又名分散/聚集(尽管英特尔 Larrabee 正朝着那个方向发展)。
我的 comp-arch.net wiki 上的一些页面是关于这个的(我写关于计算机体系结构的文章是出于我的爱好): - http://semipublic.comp-arch.net/wiki/SIMD - http://semipublic.comp-arch.net/wiki/SIMD_packed_vector - http://semipublic.comp-arch.net/wiki/Difference_between_vector_and_packed_vector - http://semipublic.comp-arch.net/wiki/Single_Instruction_Multiple_Threads_(SIMT) 尽管我很抱歉尚未编写有关 SIMD 压缩向量指令序列的页面,例如在英特尔 MMX 或 SIMD 中。
但我不希望您阅读以上所有内容。让我试着解释一下。
想象一下,当您以简单的标量方式编写时,您有一段看起来像这样的代码:
// operating on an array with one million 32b floating point elements A[1000000]
for i from 0 upto 999999 do
if some_condition(A[i]) then
A[i] = function1(A[i])
else
A[i] = function2(A[i])
其中 function1() 和 function2() 足够简单,可以内联 - 例如 function1(x) = x*x 和 function2(x) = sqrt(x)。
在 CPU 上。要使用 SSE 之类的东西,您必须 (1) 将数组分成块,例如 256 位 AVX 的大小,(2) 自己处理 IF 语句,使用掩码等。比如:
for i from 0 upto 999999 by 8 do
register tmp256b_1 = load256b(&A[i])
register tmp256b_2 = tmp256b_1 * tmp256b_1
register tmp256b_3 = _mm_sqrt_ps(tmp256b_1) // this is an "intrinsic"
// a function, possibly inlined
// doing a Newton Raphson to evaluate sqrt.
register mask256b = ... code that arranges for you to have 32 1s in the "lane"
where some_condition is true, and 0s elsewhere...
register tmp256b_4 = (tmp256b_1 & mask) | (tmp256b_3 | ~mask);
store256b(&A[i],tmp256b_4)
您可能不认为这很糟糕,但请记住,这是一个简单的示例。想象一下多个嵌套的 IF,等等。或者,假设“some_condition”是块状的,因此您可以通过跳过全部为 function1 或所有 function2 的部分来节省大量不必要的计算......
for i from 0 upto 999999 by 8 do
register mask256b = ... code that arranges for you to have 32 1s in the "lane"
where some_condition is true, and 0s elsewhere...
register tmp256b_1 = load256b(A[i])
if mask256b == ~0 then
register tmp256b_2 = tmp256b_1 * tmp256b_1
store256b(&A[i],tmp256b_2)
else mask256b == 0 then
register tmp256b_3 = _mm_sqrt_ps(tmp256b_1) // this is an "intrinsic"
store256b(&A[i],tmp256b_3)
else
register tmp256b_1 = load256b(&A[i])
register tmp256b_2 = tmp256b_1 * tmp256b_1
register tmp256b_3 = _mm_sqrt_ps(tmp256b_1)
register tmp256b_4 = (tmp256b_1 & mask) | (tmp256b_3 | ~mask);
store256b(&A[i],tmp256b_4)
我想你能得到图片吗?当您有多个数组时,它会变得更加复杂,有时数据在 256 位边界上对齐,有时则不是(这是典型的,例如,在模板计算中,您对所有对齐进行操作)。
现在,这大概是它在 GPU 上的样子:
// operating on an array with one million 32b floating point elements A[1000000]
for all i from 0 upto 999999 do
if some_condition(A) then
A = function1(A)
else
A = function2(A)
这看起来不是更像原始标量代码吗?唯一真正的区别是您丢失了数组索引 A[i]。 (实际上,一些 GPGPU 语言保留了数组索引,但我所知道的大多数都没有。)
现在,我省略了 (a) Open/CL 的类似 C 的语法,(b) 将 Open/CL 代码连接到 C 或 C++ 代码所需的所有设置(有比CUDA 或 OpenCL——它们有很多麻烦。但它们在很多地方都可用,在 CPU 和 GPU 上[**])。但我想我已经提出了问题的核心:
GPGPU 计算的关键在于您编写 SIMD,数据并行冷。但是你写它的层次比你写 CPU 风格的 SSE 代码要高。甚至比编译器内在函数还要高。
首先,GPGPU 编译器,例如OpenCL 或 CUDA 编译器,在您背后处理大量数据管理。编译器安排执行控制流、tghe IF 语句等。
顺便说一句,请注意,正如我用 [**] 标记的,有时所谓的 SIMD GPGPU 编译器可以生成将在 CPU 和 GPU 上运行的代码。 IE。 SIMD 编译器可以生成使用 CPU SIMD 指令集的代码。
但 GPU 本身具有运行此 SIMD 代码的特殊硬件支持,经过适当编译,比使用 CPU SIMD 指令在 CPU 上运行的速度要快得多。最重要的是,GPU 有更多的执行单元——例如像 AMD Bulldoser 这样的 CPU 有 2 组 128 位宽的 FMACS,即每个周期能够执行 8 个 FMAC。乘以芯片上的 CPU 数量 - 比如 8 - 每个周期可能有 64 个。而现代 GPU 每个周期可能有 2,048 个 32b FMAC。即使以 1/2 或 1/4 的时钟频率运行,也有很大的不同。
GPU 怎么会有这么多硬件?嗯,首先,它们通常是比 CPU 更大的芯片。但是,他们也倾向于不将(有人说“浪费”)硬件用于大型缓存和 CPU 所花费的无序执行之类的事情上。 CPU 尝试快速进行一项或多项计算,而 GPU 并行执行许多计算,但单独比 CPU 慢。尽管如此,GPU 每秒可以执行的计算总数远高于 CPU 可以执行的操作。
FGPU 具有其他硬件优化。例如,它们运行的线程比 CPU 多得多。 Intel CPU 每个 CPU 有 2 个超线程,在 8 个 CPU 核心芯片上提供 16 个线程,而 GPU 可能有数百个。以此类推。
作为一名计算机架构师,我最感兴趣的是,许多 GPU 都为 SIMD 控制流提供了特殊的硬件支持。它们使操作这些掩码比在运行 SSE 的 CPU 上更有效。
等等。
无论如何,我希望我已经表达了我的观点
虽然您确实必须编写 SIMD 代码才能在 GPGPU 系统(如 OpenCL)上运行。
您不应将此类 SIMD 与您必须编写以利用英特尔 SSE 的 SIMD 代码混淆。
干净多了。
越来越多的编译器允许相同的代码在 DCPU 和 GPU 上运行。 IE。他们越来越多地支持干净的“真正的 SIMD”编码风格,而不是迄今为止利用 MMX、SSE 和 AVX 所必需的虚假“伪 SIMD”编码风格。这很好——这样的代码在 CPU 和 GPU 上编程同样“好”。但 GPU 通常运行得更快。 Intel 有一篇论文叫做“揭穿 100X GPU 与 CPU 的神话:对 CPU 和 GPU 的吞吐量计算的评估”,http://www.hwsw.hu/kepek/hirek/2010/06/p451-lee.pdf。它说 GPU 平均“仅”快 2.5 倍。但这是经过大量积极优化之后的事情。 GPU 代码通常更容易编写。而且我不了解您,但我认为“仅”快 2.5 倍并没有什么好打喷嚏的。尤其是因为 GPGPU 代码通常更容易阅读。
现在,没有免费的午餐。如果您的代码自然是数据并行的,那就太好了。但有些 coede 不是。这可能会很痛苦。
而且,与所有机器一样,GPU 也有其怪癖。
但是,如果您的代码自然是数据并行的,您可能会获得极大的加速,并且代码更具可读性。
我是一名 CPU 设计师。我希望从 GPU 中借鉴很多想法,让男性 CPU 运行得更快,反之亦然。
【讨论】: