基本GPU应用:整数计算

17
长话短说,我已经做过几个交互式软件的原型。我现在使用pygame(Python sdl wrapper),一切都在CPU上完成。我现在开始将它移植到C,同时寻找现有的可能性,以利用一些GPU的算力来减轻CPU的冗余操作。然而在我的情况下,我找不到好的“指南”应该选择什么样的技术/工具。我只是阅读了大量文档,这让我的精力迅速消耗殆尽。我不确定是否有可能,所以我很困惑。
这里我画了一个非常粗略的典型应用程序骨架草图,但是考虑到它现在使用了GPU(请注意,我几乎没有关于GPU编程的实际知识),数据类型和功能必须完全保留。这就是它:
enter image description here

因此,F(A,R,P)是一些自定义函数,例如元素替换,重复等。该函数在程序生命周期内可能是恒定的,矩形形状通常不等于A形状,因此它不是原地计算。因此,它们只是通过我的函数生成的。 F的示例:重复A的行和列;用替换表中的值替换值;将一些图块组合成单个数组;在A值上进行任何数学函数,等等。如上所述,所有这些都可以在CPU上轻松完成,但应用程序必须非常流畅。顺便说一句,在纯Python中,添加了几个基于numpy数组的可视化功能之后,它变得不可用。 Cython有助于快速制作自定义函数,但是源代码已经有点像沙拉了。
问题:
  • 此模式反映了某些(标准)技术/开发工具吗?
  • CUDA是我正在寻找的东西吗?如果是,请提供一些与我的应用程序结构相符的链接/示例。
我意识到这是一个很大的问题,因此如果需要,我会提供更多细节。
更新
这里是我位图编辑器原型的两个典型计算的具体示例。因此,编辑器使用索引,数据包括具有相应位掩码的层。我可以确定层的大小,掩码与层相同大小,例如,所有层都具有相同的大小(1024 ^ 2像素= 32位值的4 MB)。我的调色板有1024个元素(32 bpp格式的4千字节)。
考虑现在我想做两件事:
步骤1。 我想将所有层压平为一个。假设A1是默认层(背景),层“A2”和“A3”具有掩码“m2”和“m3”。在python中,我会写:
from numpy import logical_not
...
Result = (A1 * logical_not(m2) + A2 * m2) * logical_not(m3) + A3 * m3

由于数据是独立的,我相信它必须按并行块的数量成比例地提高速度。

步骤 2。现在我有一个数组,并想用一些调色板进行“着色”,这将是我的查找表。从现在开始,我发现同时读取查找表元素存在问题。enter image description here

但是我的想法是,也许可以为所有块复制调色板,这样每个块都可以读取自己的调色板?像这样:enter image description here


FYI:有一个名为Theano的Python库可以使用GPU。它将符号表达式编译成在GPU上运行的CUDA代码。 - Stefan Falk
如果有大量独立数据通过(每秒数百MB),那么它确实可以加快速度。我唯一看到的问题是“结果矩形可以是任意大小”。我从未听说过通过内核进行分配。如果在开始之前已知其大小,则可以,但必须在GPU内核启动之前定义数组。因此,它基本上取决于F(输入和输出)和A的大小。 - blind.wolf
@blind.wolf 是的,大小已知,只是它不能与A具有相同的形状,例如在整数数组升级的情况下。 - Mikhail V
为什么会有人投票关闭这个问题???? - barak manos
@buttifulbuttefly,我认为是这样的。想象一下你有几个数组,并且你想获取平均值,例如像 Photoshop 中的图层,但只有索引值。现在移动一个图层并实时查看更改结果。但是首先,我想要非常非常简单的东西,就像在替换或行/列重复的示例中提到的那样。如果我能让我的数组充满调色板值,它本身就是一个不错的改进。而且替换可以完全并行化,我是对的吗? - Mikhail V
3个回答

2
当你的代码高度并行(即处理阶段之间存在很少或没有数据依赖关系)时,你可以选择CUDA(对同步控制有更细粒度的控制)或OpenCL(类似于可移植的OpenGL API,用于与GPU进行内核处理)。我们所做的大部分加速工作都是在OpenCL中完成的,它与OpenGL和DirectX都有很好的互操作性,但我们也使用CUDA进行相同的设置。 CUDA和OpenCL之间的一个重要区别是,在CUDA中,您可以编译内核一次,然后在应用程序中进行延迟加载(和/或链接),而在OpenCL中,编译器会与OpenCL驱动程序堆栈协作,以确保内核在应用程序启动时被编译。
如果您正在使用Microsoft Visual Studio,则经常被忽视的另一种选择是C++AMP,这是一个友好且直观的C ++语法API,适用于那些不想深入研究OpenCL / CUDA API逻辑曲折的人。这里的一个巨大优势是,如果系统中没有GPU,则代码也可以运行,但是这样做就没有太多调整性能的选项。尽管如此,在许多情况下,这仍然是编写概念验证代码的快速有效的方法,并稍后重新实现CUDA或OpenCL的部分内容。
OpenMP和Thread Building Blocks只有在存在同步问题和大量数据依赖时才是良好的替代方案。使用工作线程的本地线程也是一种可行的解决方案,但仅当您对如何设置不同进程之间的同步点以使线程在争夺优先级时不会饿死对方有一个好主意时。这更难以正确实现,并且必须使用Parallel Studio等工具。但是,如果您编写GPU代码,则必须使用NVida NSight。

附录:

正在开发一个名为Quasar(http://quasar.ugent.be/blog/)的新平台,它使您能够使用类似于Matlab的语法编写数学问题,并具有完全支持c/c++/c#或java集成,并将您的“核心”代码交叉编译(LLVM、CLANG)到任何底层硬件配置。它生成CUDA ptx文件,或在openCL上运行,甚至使用TBB的CPU,或者混合使用它们。使用一些别名,您可以装饰算法,以便底层编译器可以推断类型(您也可以显式使用严格类型),因此您可以完全将类型繁重的工作留给编译器。公平地说,在撰写本文时,该系统仍在进行中,第一个OpenCL编译的程序仅正在测试中,但最重要的好处是快速原型设计,与优化的cuda相比,性能几乎相同。

感谢提供的信息。我的主要问题更多地在表面上。我只想要几个循环,扫描键盘状态并调用函数。现在我使用Python(带有VIM)。到目前为止,我可以在没有调试的情况下完成一些事情。将其移植到C/C++(对于这种应用程序,我几乎不需要C++)可能会解决性能问题,并且仍然是标准方法,也受其他工具的支持。简而言之,如何将普通的C程序(例如SDL)与并行代码结合起来? - Mikhail V
在我回答之前,请注意线程和并行编程之间的区别,因为线程是关于同步和调度作业,而GPU并行编程是组织代码和数据的意识形态方法,以便它适合并行硬件执行。坦率地说,第一种并行性明确处理复杂的数据依赖关系,而GPU并行性根本不喜欢这种情况。从您的描述来看,pthread似乎是您需要的,例如:请参见http://softpixel.com/~cwright/programming/threads/threads.c.php - StarShine
不,现在我不想要CPU并行化或CPU端的线程。我的意思是,我有一个主循环(抱歉,之前写成了“循环”),所以我扫描键盘状态然后进行一些数学计算,但结果,即标准化的数组,希望使用GPU处理。因此,我想我只需要从我的程序中调用一些“特殊”的函数。仍然没有找到清晰的说明。这只是典型的实时交互应用程序。另外,我不需要来自GPU的反馈。 - Mikhail V
CUDA和OpenCL库本质上都是C库,因此所有教程都应该适用于C,而不需要太多麻烦。 - StarShine
是的,我明白了。然而,“所有教程”对我来说是个问题。我曾经多次从Cuda文档和其他一些文档开始,但它们带我去了我不想要的地方。我认为讨论教程是离题的,这就是为什么我问是否有人可以分享相关的简单示例。我现在尝试使用http://www.fixstars.com/en/opencl/book,它相当易懂。 - Mikhail V

1
你需要做的就是使用高频分发将值快速发送到GPU,然后显示一个函数的结果,该函数基本上是纹理查找和一些参数。我认为只有满足以下两个条件,才值得在GPU上解决此问题:1.优化A[]的大小以使传输时间不相关(请参阅http://blog.theincredibleholk.org/blog/2012/11/29/a-look-at-gpu-memory-transfer/);2.查找表不太大和/或查找值以最大程度地利用缓存组织,通常在GPU上进行随机查找可能会很慢,理想情况下,您可以预先将R[]值加载到每个A[]缓冲区的共享内存缓冲区中。如果您可以积极回答这两个问题,那么只有考虑使用GPU解决您的问题,否则这两个因素将超过GPU可以为您提供的计算加速。
另外一个你可以考虑的事情是尽可能地重叠传输和计算时间,以尽量隐藏CPU->GPU数据传输速度慢的问题。
关于你的F(A, R, P)函数,你需要确保你不需要知道F(A, R, P)[0]的值才能知道F(A, R, P)[1]的值,因为如果需要,你需要重写F(A, R, P)来解决这个问题,使用一些并行化技术。如果你有有限数量的F()函数,那么可以编写每个F()函数的并行版本供GPU使用,但如果F()是用户定义的,则问题会变得有点棘手。
我希望这提供了足够的信息,让你对是否应该使用GPU解决你的问题有一个明智的猜测。
编辑

阅读了您的编辑,我认为是可以的。 调色板可以适合共享内存(请参见GPU shared memory size is very small - what can I do about it?),这非常快,如果您有多个调色板,则可以将16KB(大多数卡上的共享内存大小)/ 4KB每个调色板=每个线程块的4个调色板。

最后警告一下,整数操作在GPU上并不是最快的,如果必要的话,请在实现算法并且它作为廉价优化时考虑使用浮点数。


非常感谢,这解答了我问题的一部分。我已经更新了,提供了实际的例子。从理论上说,它符合标准,但我不确定,尤其是关于查找表缓存的部分。你能不能看一下并告诉我它是否会像我想象的那样工作?因为这两个计算实际上对于应用程序来说是最典型的。 - Mikhail V
谢谢,这是我目前所需的信息。新千年位图编辑器即将到来 :) - Mikhail V

1

OpenCL/CUDA之间没有太大的区别,因此选择哪个更适合你。只要记住,CUDA将限制您使用NVidia GPU。

如果我正确理解了您的问题,那么内核(在GPU上执行的函数)应该很简单。它应该遵循这个伪代码:

kernel main(shared A, shared outA, const struct R, const struct P, const int maxOut, const int sizeA)
  int index := getIndex() // get offset in input array
  if(sizeA >= index) return // GPU often works better when n of threads is 2^n
  int outIndex := index*maxOut // to get offset in output array
  outA[outIndex] := F(A[index], R, P)
end

函数F应该内联,您可以使用switch或if来处理不同的函数。由于F的输出大小未知,因此必须使用更多的内存。每个内核实例必须知道正确的内存写入和读取位置,因此必须有一些最大的大小(如果没有,则所有这些都是无用的,您必须使用CPU!)。如果不同的大小是稀疏的,则我会在将数组返回到RAM后计算这些不同的大小,并使用CPU计算这些少量大小,同时使用一些零或指示值填充outA。
数组的大小显然为length(A)* maxOut = length(outA)。
我忘了提到,如果执行F的情况大多不同(相同的源代码),那么GPU将对其进行序列化。 GPU多处理器具有几个连接到相同指令缓存中的核心,因此它必须对代码进行序列化,这对于所有核心来说并不相同! OpenMP或线程是解决这种问题的更好选择!

好的,你的意思是这样的事情完全可以做到。那我就用OpenCL试试看,我非常感兴趣。如果有什么有趣的结果,我会更新的。 - Mikhail V
然而,我不确定真正的加速效果。如果它确实遵循这个模式,那么加速应该是显著的。问题在于F。如果它的执行确实是随机的,那么它将大多数情况下被序列化,最好使用OpenMP或线程来处理,因为GPU将会序列化大部分代码。(对于序列化的代码,CPU至少快3倍) - blind.wolf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接