2011-10-07 31 views
2

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

回答

7

那么,GPGPU只支持SIMD执行并不完全正确。许多GPU都有一些非SIMD组件。但总的来说,要充分利用GPU,您需要运行SIMD代码。

但是,不一定写SIMD指令。即GPU SIMD是而不是与CPU SIMD相同 - 即不同于编写代码以利用x86 SSE(Stream SIMD Extensions)等。事实上,作为通过CPU SIMD给你的人之一(我很沉重参与了英特尔MMX,这是其中最早的一个,并且已经跟随FP SIMD的发展)。我经常感到有义务纠正那些认为像英特尔这样的CPU有SIMD指令的人。我更愿意考虑它们的打包矢量指令,尽管我勉强称它们为SIMD打包矢量指令集,只是因为每个人都误用了这个名称。我还强调,诸如MMX和SSE的CPU SIMD指令集可能具有SIMD打包的矢量执行单元 - 整数和浮点ALU等 - 但它们没有SIMD控制流,并且通常不具有SIMD存储器访问(又名分散/聚集(尽管英特尔Larrabee正朝着这个方向发展))。

我这个comp-arch.net维基有些(我写的计算机体系结构我的爱好): - 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压缩矢量指令序列,就像Intel 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]) 

其中功能1()和函数2()是很简单的内联 - 说函数1(x)= x * x和函数2(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”是块状,这样你可能会节省很多不必要的计算,通过跳过部分,其中它是所有功能1或全部功能2 ...

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。即SIMD编译器可以生成使用CPU SIMD指令集的代码。

但GPU本身具有特殊的硬件支持,它可以运行此SIMD代码,编译得当,比使用CPU SIMD指令在CPU上运行得快得多。最重要的是,GPU具有更多的执行单元 - 例如,象AMD Bulldoser这样的CPU有2组128位宽度的FMACS,即每周期能够执行8个FMAC。计算一个芯片上CPU的数量 - 例如8 - 每个周期可能有64个CPU。而现代GPU每周期可能有2,048 32b FMAC。即使以1/2或1/4的时钟频率运行,这也是一个很大的区别。

GPU如何有更多的硬件?那么,首先,他们通常比CPU更大的芯片。但是,他们也倾向于不花费(有些人说“浪费”)硬件,比如大型缓存和CPU花费在乱序执行上。 CPU试图快速完成一个或几个计算,而GPU并行执行许多计算,但比CPU慢得多。尽管如此,GPU每秒可以完成的计算总数远远高于CPU所能做到的。

FGPU还有其他硬件优化。例如,他们运行的线程多于CPU。而英特尔CPU每个CPU有2个超线程,给你8个CPU核心芯片上的16个线程,GPU可能有数百个。等等。

作为一名计算机架构师,我最感兴趣的是,许多GPU对SIMD控制流程都有特殊的硬件支持。与运行SSE的CPU相比,它们可以更有效地操作这些掩码。

依此类推。


无论如何,我希望我已经说的

  • 虽然你必须写SIMD代码为GPGPU系统(如OpenCL的)上运行。

  • 您不应该将这类SIMD与您必须编写以利用Intel SSE的SIMD代码混淆。

它更干净。

越来越多的编译器允许相同的代码在DCPU和GPU上运行。即他们越来越支持干净的“真正的SIMD”编码风格,而不是像现在一直利用MMX和SSE和AVX一样必需的假“伪SIMD”编码风格。这很好 - 这样的代码在CPU和GPU上编程同样“好”。但GPU经常运行得更快。英特尔有一篇名为“揭开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运行得更快,反之亦然。