xak*_*p35 2 parallel-processing foreach gpgpu range opencl
我认为计算加速器 (GPU) 是一些 SP - “流处理器”,每个SP都包含一些固定数量的 ALU 核心,以 SIMD 方式运行。但与 cpu 线程不同的是,SP 会一起触发,并有一定的跨度。这也称为合并。
例如,我有数组 A、B 和 C。我会知道并管理它们的内部结构(无论是 1D 数组还是 5D 数组)——这不是 GPU 感兴趣的。我只是这样告诉它:“取出这对只读存储器 A 和 B。取出一个只写存储器 C。执行某个指令序列 N 次。”
GPU“最了解”其内部“SP”(或“CU”)计数和缓存,可以直接获取这些信息并将任务削减到相同的块中。
所以硬币的正面是每个 DRAM 都是扁平的。因此,PC 中的一切本质上都是一维的。我不明白什么是 2D、3D 范围以及它们的用途。我们不能在任何地方都使用一维吗?
另一方面,我们假设这已经完成,因为 openCL 方法声称非常灵活,甚至迫使我们为其提供内部数组结构。现在我有42维的数据了!为什么不支持但只支持3维?
那么什么是局部组、全局组、ndranges 维度以及如何计算它们?
您能否提供一些例子,说明多维范围至关重要,或者至少有利于使用?如何划分本地大小、缓存大小和全局大小?
这是我不理解的参数列表,并且完全混乱:
CL_DEVICE_GLOBAL_MEM_CACHE_SIZE
CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE
CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE
CL_DEVICE_LOCAL_MEM_SIZE
CL_DEVICE_MAX_WORK_ITEM_SIZES
CL_DEVICE_IMAGE2D_MAX_HEIGHT
CL_DEVICE_IMAGE3D_MAX_HEIGHT
CL_DEVICE_MAX_SAMPLERS
CL_DEVICE_MAX_COMPUTE_UNITS
Run Code Online (Sandbox Code Playgroud)
是否有一些关于普通程序员如何使用它们的通用公式,只是为了确保他的工作将在任何 GPU 上足够有效地分配?
好的,我会尽力解释这一点,但您在一篇文章中提出了一大堆问题,看来您缺乏 OpenCL 的基本抽象。
主机:主机决定 OpenCL 发生的情况。那是运行你的程序的处理器。
计算设备:这是您的代码将在其上运行的硬件。显卡 (GPU) 是单个设备,多核 CPU 也是如此。如果您的计算机上有两个 GPU,则可以让您的程序同时在两个设备上运行。
计算单元:在设备内,所有核心(Nvidia 的 CUDA 核心、AMD 的流处理器)都被分成共享公共本地内存的组。每个计算单元在概念上都可以看作是一个小型 SIMD 处理器。组大小因设备而异,但通常为 32 或 64。(对于我的 GTX 970,我在 13 个计算单元中拥有 1664 个 CUDA 核心,因此为 128 个)。我认为没有直接的方法可以使用 来查询clGetDeviceInfo,但是您可以轻松地针对给定的显卡找出它。
处理元素:这就是我们对 GPU 中单个核心的命名方式。他们没有任何记忆,只有寄存器。请注意,在任何给定时间,同一计算单元的每个处理元件都将同步运行相同的程序。if/else如果代码中有一堆逻辑 ( ) 语句,并且某些处理元素采用与其他处理元素不同的分支,则所有其他处理元素将等待不执行任何操作。
程序:即使对我来说,这也或多或少是清楚的。这是您的程序加载到内存中并需要构建/编译。一个程序可能包含多个单独调用的函数(内核)。
内核:简单地说,这就是您将在设备上运行的功能。内核的实例将在处理元件上运行。您的内核有很多很多实例同时运行。在内核中,可以获得有关正在运行的处理元素的一些信息。这是通过一些与 clEnqueueNDRangeKernel 的参数密切相关的基本函数来完成的(见下文)。
内存: 就内存而言,每个计算设备 (GPU) 都有一个可以从主机写入的全局内存。然后,每个计算单元将具有有限数量的本地内存 (CL_DEVICE_LOCAL_MEM_SIZE),由该计算单元的处理元件共享。您可以分配的缓冲区大小有许多限制,但通常这不是问题。您可以查询不同的 CL_DEVICE_x 参数来获取这些数字。全局内存有一个“恒定”部分,但我不会讨论它,因为它不会给讨论带来任何东西。
当你想在 GPU 上执行计算时,你需要一个内核和 GPU 内存中的一些缓冲区。主机(CPU)应将内存传输到全局内存中的缓冲区中。它还应该设置内核所需的参数。然后,它应该告诉 GPU 使用 clEnqueueNDRangeKernel 调用内核。这个函数有很多参数......
globalWorkSize:每个维度,内核必须运行才能解决问题的次数。维度的数量是任意的,标准规定计算设备需要支持至少 3 个维度,但某些 GPU 可能支持更多维度。这并不重要,因为任何 ND 问题都可以分解为多个一维问题。
localWorkSize:这是计算单元在每个维度上执行的工作大小。通常,您希望使用与计算单元中的处理元素数量相对应的值(通常为 32 或 64,请参见上文)。请注意,localWorkSize 必须均匀划分 globalWorkSize。0 == (globalWorkSize % localWorkSize)。
让我们举个例子。假设我有一个包含 1024 个数字的一维数组,我只想对该数组中的每个值求平方。globalWorkSize 为 1024,因为我希望独立处理每个数字,并且我会将 localWorkSize 设置为我的计算单元中处理元素的最高数量,该数量可以整除 1024(对于我的 GTX970,我将使用 128)。我的问题是一维,所以我将向该参数写入 1。
请记住,如果您使用的数量小于(或大于)计算单元中处理元素的数量,则其他计算单元只会消耗时钟周期而不执行任何操作。我本来可以说我想要 localWorkSize 为 2,但是每个计算单元都会浪费 126/128 个处理元素,这并不是真正高效。
通过设置globalWorkSize = 1024和localWorkSize = 128,我只是告诉我的 GPU 在 (1024/128 = 8) 个计算单元上运行内核 1024 次。我将拥有 1024 个处理元素(CUDA 核心),每个处理元素对缓冲区的 1 个元素执行操作。
现在,处理元素如何知道它必须在我的缓冲区中计算什么值?这就是工作项功能发挥作用的地方。
其中有几个,但对于这个例子我只关心get_global_id(uint nDimensions). 它根据 globalWorkSize 返回给定维度的全局 ID。在我们的例子中,我们的问题是 1d,因此get_global_id(0)将返回 [0, globalWorkSize] 之间的索引。每个处理元素的索引都不同。
示例内核:
__kernel MakeSquared(__global double* values) {
size_t idx = get_global_id(0);
values[idx] = values[idx] * values[idx];
}
Run Code Online (Sandbox Code Playgroud)
编辑:本地内存使用情况示例:
__kernel MakeSquared(__global double* values, __local double* lValues) {
size_t idx = get_global_id(0);
size_t localId = get_local_id(0);
lValues[localId] = values[idx];
// potentially some complex calculations
lValues[localId] = lValues[localId] * lValues[localId];
values[idx] = lValues[localId];
}
Run Code Online (Sandbox Code Playgroud)
还有很多东西要说,但我想我已经涵盖了基础知识。