Opencl 使用本地内存加速计算

Opencl 使用本地内存加速计算,opencl,Opencl,这应该很容易,但我的OpenCL技能已经完全生锈了。:) 我有一个简单的内核,可以对两个数组求和: __kernel void sum(__global float* a, __global float* b, __global float* c) { __private size_t gid = get_global_id(0); c[gid] = log(sqrt(exp(cos(sin(a[gid]))))) + log(sqrt(exp(cos(sin(b[gid]))

这应该很容易,但我的OpenCL技能已经完全生锈了。:)

我有一个简单的内核,可以对两个数组求和:

__kernel void sum(__global float* a, __global float* b, __global float* c)
{
    __private size_t gid = get_global_id(0);

    c[gid] = log(sqrt(exp(cos(sin(a[gid]))))) + log(sqrt(exp(cos(sin(b[gid])))));
}
很好用

现在我尝试使用本地内存,希望它能加快速度:

__kernel void sum_with_local_copy(__global float* a, __global float* b, __global float* c, __local float* tmpa, __local float* tmpb, __local float* tmpc)
{
    __private size_t gid = get_global_id(0);
    __private size_t lid = get_local_id(0);
    __private size_t grid = get_group_id(0);
    __private size_t lsz = get_local_size(0);

    event_t evta = async_work_group_copy(tmpa, a + grid * lsz, lsz, 0);
    wait_group_events(1, &evta);

    event_t evtb = async_work_group_copy(tmpb, b + grid * lsz, lsz, 0);
    wait_group_events(1, &evtb);

    tmpc[lid] = log(sqrt(exp(cos(sin(tmpa[lid]))))) + log(sqrt(exp(cos(sin(tmpb[lid])))));

    event_t evt = async_work_group_copy(c + grid * lsz, tmpc, lsz, 0);
    wait_group_events(1, &evt);
}
但是这个内核有两个问题:

  • 它的速度大约是原始实现的3倍

  • 结果从索引64开始是错误的

我的本地大小是最大工作组大小

因此,我的问题是:

1) 我是否遗漏了一些明显的东西,还是真的有微妙之处

2) 如何使用本地内存来加速计算

3) 我是否应该在内核中循环,以便每个工作项执行多个操作


提前感谢。

您的简单内核已经是w.r.t工作组的最佳性能

只有当工作组中的多个工作项从本地内存中的同一地址读取时,本地内存才会提高性能。由于内核中没有共享数据,所以将数据从全局内存传输到本地内存不会带来任何好处,因此速度会减慢


至于第3点,您可能会看到每个线程处理多个值的好处(取决于您的计算成本和您拥有的硬件)。

您的简单内核已经是最佳的w.r.t工作组性能

只有当工作组中的多个工作项从本地内存中的同一地址读取时,本地内存才会提高性能。由于内核中没有共享数据,所以将数据从全局内存传输到本地内存不会带来任何好处,因此速度会减慢

至于第3点,您可能会看到每个线程处理多个值的好处(取决于您的计算成本和您拥有的硬件)。

增加了Kyle的功能:必须是从同一地址读取多个工作项;如果只是每个工作项本身从同一地址多次读取,那么本地内存也不会有任何帮助;只需使用工作项的私有内存,即在内核中定义的变量

此外,还有一些与本地内存使用无关的要点:

  • log(sqrt(exp(x))=log(exp(x))/2=x/2…假设它是自然对数
  • log(sqrt(exp(x))=log(exp(x))/2=x/(2ln(2))…假设它是以2为底的对数。当然要提前计算ln(2)
  • 如果你真的有一些复杂的函数,你最好使用一个扩展。例如,你的函数扩展到1/2-x^2/4+(5 x^4)/48+O(x^6)(顺序5)

    最后一项是错误项,您可以从上面对其进行绑定,以选择适当的展开顺序;对于“性能良好”的函数,错误项不应太高。泰勒展开计算甚至可能受益于进一步的并行化(但同样,它可能不会)

增加Kyle的功能:它必须是多个工作项从同一地址读取;如果只是每个工作项本身从同一地址读取多次,那么本地内存对您没有任何帮助;只需使用工作项的私有内存,即您在内核中定义的变量

此外,还有一些与本地内存使用无关的要点:

  • log(sqrt(exp(x))=log(exp(x))/2=x/2…假设它是自然对数
  • log(sqrt(exp(x))=log(exp(x))/2=x/(2ln(2))…假设它是以2为底的对数。当然要提前计算ln(2)
  • 如果你真的有一些复杂的函数,你最好使用一个扩展。例如,你的函数扩展到1/2-x^2/4+(5 x^4)/48+O(x^6)(顺序5)

    最后一项是错误项,您可以从上面对其进行绑定,以选择适当的展开顺序;对于“性能良好”的函数,错误项不应太高。泰勒展开计算甚至可能受益于进一步的并行化(但同样,它可能不会)


您可能知道,在执行内核时,可以使用以下方法显式设置本地工作组大小(LWS):

clEnqueueNDRangeKernel( ... bunch of args include Local Work Size ...);
正如.但正如Kyle已经提到的,您实际上不必这样做,因为当您为LWS参数传入NULL时,OpenCL会尝试为LWS选择最佳值

实际上,规范中说:“本地工作大小也可以是空值,在这种情况下,OpenCL实现将决定如何将全局工作项分解为适当的工作组实例。”

我很想知道在你的情况下这是如何实现的,所以我设置了你的计算,以验证OpenCL在我的设备上选择的默认值的性能

如果您感兴趣,我可以设置一些任意数据:

int n = powl(2, 20);
float* a = (float*)malloc(sizeof(float)*n);
float* b = (float*)malloc(sizeof(float)*n);
float* results = (float*)malloc(sizeof(float)*n);

for (int i = 0; i<n; i++) {
    a[i] = (float)i;
    b[i] = (float)(n-i);
    results[i] = 0.f;
}
接下来,我检查了OpenCL选择的默认值(为LWS传递NULL),这对应于通过分析找到的最佳值,即LWS=256

因此,在您设置的代码中,您发现了一种次优情况,如前所述,最好让OpenCL为本地工作组选择最佳值,特别是在内核中一个工作组中的多个工作项之间没有共享数据的情况下

至于您得到的错误,您可能违反了一个约束(来自规范): 工作组中的工作项总数必须小于或等于CL_DEVICE_MAX_work_group_大小


您是否通过查询设备的CL_设备\u最大\u工作\u组\u大小来详细检查了这一点?

您可能知道,在执行内核时,可以使用以下方法显式设置本地工作组大小(LWS):

clEnqueueNDRangeKernel( ... bunch of args include Local Work Size ...);
正如.但正如Kyle已经提到的,您实际上不必这样做,因为当您为LWS参数传入NULL时,OpenCL会尝试为LWS选择最佳值

实际上,规范中说:“本地工作大小也可以是空值,在这种情况下,Op
   LWS     time(sec)
    2       14.004
    4       6.850
    8       3.431
   16       1.722
   32       0.866
   64       0.438
  128       0.436
  256       0.436