【问题标题】:Does GPGPU programming only allow the execution of SIMD instructions?GPGPU 编程是否只允许执行 SIMD 指令?
【发布时间】:2011-10-07 03:37:37
【问题描述】:

GPGPU 编程是否只允许执行 SIMD 指令? 如果是这样,那么重新编写一个具有 设计为在通用 CPU 上运行以在 GPU 上运行?还有一个 可以转换为 SIMD 架构的算法模式?

【问题讨论】:

    标签: gpgpu simd cpu-architecture


    【解决方案1】:

    嗯,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 运行得更快,反之亦然。

    【讨论】:

      猜你喜欢
      • 2017-11-22
      • 2015-06-05
      • 2021-01-13
      • 2019-01-19
      • 2015-10-23
      • 2010-11-02
      • 1970-01-01
      • 1970-01-01
      • 2016-06-04
      相关资源
      最近更新 更多