【问题标题】:Cost of OpenCL get_local_id()OpenCL get_local_id() 的成本
【发布时间】:2013-09-26 12:12:15
【问题描述】:

我有一个简单的扫描内核,它在一个循环中计算几个块的扫描。我注意到当 get_local_id() 存储在局部变量中而不是在循环中调用它时,性能会有所提高。所以用代码总结一下:

__kernel void LocalScan_v0(__global const int *p_array, int n_array_size, __global int *p_scan)
{
    const int n_group_offset = get_group_id(0) * SCAN_BLOCK_SIZE;
    p_array += n_group_offset;
    p_scan += n_group_offset;
    // calculate group offset

    const int li = get_local_id(0); // *** local id cached ***
    const int gn = get_num_groups(0);
    __local int p_workspace[SCAN_BLOCK_SIZE];
    for(int i = n_group_offset; i < n_array_size; i += SCAN_BLOCK_SIZE * gn) {
        LocalScan_SingleBlock(p_array, p_scan, p_workspace, li);

        p_array += SCAN_BLOCK_SIZE * gn;
        p_scan += SCAN_BLOCK_SIZE * gn;
    }
    // process all the blocks in the array (each block size SCAN_BLOCK_SIZE)
}

在 GTX-780 上的吞吐量为 74 GB/s,而这个:

__kernel void LocalScan_v0(__global const int *p_array, int n_array_size, __global int *p_scan)
{
    const int n_group_offset = get_group_id(0) * SCAN_BLOCK_SIZE;
    p_array += n_group_offset;
    p_scan += n_group_offset;
    // calculate group offset

    const int gn = get_num_groups(0);
    __local int p_workspace[SCAN_BLOCK_SIZE];
    for(int i = n_group_offset; i < n_array_size; i += SCAN_BLOCK_SIZE * gn) {
        LocalScan_SingleBlock(p_array, p_scan, p_workspace, get_local_id(0));
        // *** local id polled inside the loop ***

        p_array += SCAN_BLOCK_SIZE * gn;
        p_scan += SCAN_BLOCK_SIZE * gn;
    }
    // process all the blocks in the array (each block size SCAN_BLOCK_SIZE)
}

在同一硬件上只有 70 GB/s。唯一的区别是对 get_local_id() 的调用是在循环内部还是外部。 LocalScan_SingleBlock() 中的代码在this GPU Gems article 中有很多描述。

现在这带来了一些问题。我一直认为线程 id 存储在某个寄存器中,并且访问它的速度与访问任何线程局部变量一样快。情况似乎并非如此。我一直习惯于将本地 id 缓存在一个变量中,而老“C”程序员不愿意在循环中调用函数,如果他希望它每次都返回相同的值,但我没有不认真地认为这会有所作为。

关于为什么会这样的任何想法?我没有对编译的二进制代码进行任何检查。有没有人有同样的经历? CUDA 中的threadIdx.x 是否一样? ATI 平台怎么样?这种行为是在某处描述的吗?我快速浏览了 CUDA 最佳实践,但没有找到任何东西。

【问题讨论】:

  • 请不要删除 CUDA 标签。虽然代码本身不在 CUDA 中,但问题在 NVIDIA 硬件上有所体现,并且与 CUDA 的 threadIdx 的实现方式以及它如何影响程序的 runitme 密切相关。

标签: cuda opencl nvidia


【解决方案1】:

这只是一个猜测,但根据 Khronos 页面

http://www.khronos.org/registry/cl/sdk/1.0/docs/man/xhtml/get_local_id.html

get_local_id() 未定义为返回常量值(仅 size_t)。这可能意味着,就编译器所知,与常量 local_id 相比,它可能不允许执行某些优化,因为函数值的返回在编译器眼中可能会发生变化(即使它不会在每个线程)

【讨论】:

  • 如果 NVIDA 那样离开它真的很愚蠢,特别是因为在 CUDA 中 threadIdx 是一个变量而不是一个函数。通过将 get_local_id() 声明为宏可以轻松解决。此外,人们会期望在某处读到它。不过,这是一个不错的猜测。
  • 好吧,opencl 规范所说的并不取决于 nvidia,如果问题是编译器优化,它是一个无法优化的非常量函数,那么它可能与threadidx 在硬件中表示。另外,宏不是常数而不是非常数吗?根据链接中对规范的实际引用,它专门在“内置功能”部分和“工作项相关功能”下,这意味着它可能另外技术上将其实现为不正确一个宏。只是更多的猜测
  • NVIDIA 是编写编译器的人。您将了解到,当涉及到供应商的实现时,规范不是法律:)。我的意思是 OpenCL 编译器只会 #define get_local_id(coord) (threadIdx.x * (~(coord | coord >> 1) & 1) + threadIdx.y * ...) 看起来像一个函数并计算编译时常量。并不是说他们需要这样做,但也许想象起来更简单。
  • 你可能是对的,在进入 PTX 程序集和所有这些之后,我注意到编译器不会重用表达式,例如 e.g. “get_local_id() * 2 + 1”。所以它似乎期待它在执行过程中发生变化。好吧,如果你问我,那真是愚蠢。另一方面,使用 get_local_id() 通常是减少寄存器使用的一种方法。与 get_local_size() 相同,即使使用 reqd_work_group_size 属性也是如此。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-16
  • 2023-04-01
  • 2012-03-12
  • 1970-01-01
  • 2017-03-27
  • 2012-05-11
相关资源
最近更新 更多