【问题标题】:Uniformly Load 128-bit Data from Constant Memory从常量存储器统一加载 128 位数据
【发布时间】:2012-07-25 09:10:35
【问题描述】:

给定一个 CUDA 向量类型 int4,我如何从常量内存中加载 128 位数据。

这似乎不起作用:

#include <stdio.h>
#include <cuda.h>

__constant__ int constant_mem[4];
__global__ void kernel(){
    int4 vec;
    vec = constant_mem[0];
}
int main(void){return 0;}

在第七行,我尝试将常量内存中的所有 4 个整数值加载到 128 位向量类型中。此操作导致以下编译错误:

vectest.cu(7): error: no operator "=" matches these operands
            operand types are: int4 = int

另外,是否可以直接访问向量类型而无需强制转换,如下所示:

int data = vec[0];

PTX 程序集中的 Switch 语句:

    @%p1 bra    BB1_55;

    setp.eq.s32     %p26, %r1, 1;
    @%p26 bra   BB1_54;

    setp.eq.s32     %p27, %r1, 2;
    @%p27 bra   BB1_53;

    setp.ne.s32     %p28, %r1, 3;
    @%p28 bra   BB1_55;

    mov.u32     %r961, %r61;
    bra.uni     BB1_56;

BB1_53:
    mov.u32     %r961, %r60;
    bra.uni     BB1_56;

BB1_54:
    mov.u32     %r961, %r59;
    bra.uni     BB1_56;

BB1_55:
    mov.u32     %r961, %r58;

BB1_56:

【问题讨论】:

  • 请提供更多上下文。 “不起作用”是什么意思?显示未定义类型和内存空间的代码 sn-p 不是很有帮助。理想情况下,您发布的代码应该简短且完全独立。当他们不必在 cmets 中猜测或要求澄清时(就像在您的上一个问题中一样),它使那些可能会帮助您的人的工作变得更容易
  • @talonmies 对不起,我不清楚,我的意思是说代码不会编译。我已更新问题以包含编译错误。我还包含了我正在尝试编译的基本代码。
  • 对于第一种情况,听起来您想要vector = * reinterpret_cast&lt;int4 *&gt;(&amp;constant_mem) 之类的东西,但为什么不在第二种情况下访问 int4 中的成员呢?我在这里错过了什么吗?
  • @talonmies 我已经通过实际将常量内存声明为 int4 找到了第一种情况的解决方法。对于第二种情况,我需要根据线程 ID 访问数据。所以我不能通过简单地使用vector.x、vector.y等来访问它。我可以将数据转换为整数数组,但我不确定这是否安全,它也不会很干净看着。

标签: types vector cuda structure gpgpu


【解决方案1】:

在第一种情况下,强制转换可能是最简单的解决方案,所以像这样:

__constant__ int constant_mem[4];
__global__ void kernel(){
    int4 vec = * reinterpret_cast<int4 *>(&constant_mem);
}

(在浏览器中编写的免责声明,未经编译或测试,使用风险自负)

使用 C++ reinterpret_cast 运算符将强制编译器发出 128 位加载指令。

在第二种情况下,听起来您想使用 128 位内存事务直接寻址存储在 128 位向量类型数组中的 32 位字。这需要一些辅助函数,可能类似于:

__inline__ __device__ int fetch4(const int4 val, const int n)
{
     (void) val.x; (void) val.y; (void) val.z; (void) val.w;
     switch(n) {
         case 3:
            return val.w;
         case 2: 
            return val.z;
         case 1:
            return val.y;
         case 0:
         default:
            return val.x;
    }
}

__device__ int index4(const int4 * array, const int n)
{
    int div = n / 4;
    int mod = n - (div * 4);

    int4 val = array[div]; // 128 bit load here

    return fetch4(val, mod);
}

__constant__ int constant_mem[128];
__global__ void kernel(){
    int val = index4(constant_mem, threadIdx.x);
}

(在浏览器中编写的免责声明,未经编译或测试,使用风险自负)

在这里,我们通过读取整个 int4 值并解析其内容来强制执行 128 位事务(强制转换为 void 是旧版本的 open64 编译器所必需的咒语,如果它认为成员未使用,则倾向于优化向量加载)。执行索引需要一些 IOP 开销,但如果生成的事务的负载带宽更高,它们可能是值得的。 switch 语句可能是使用条件执行编译的,所以不应该有分支分歧惩罚。请注意,非常随机地访问 int4 值数组可能会浪费大量带宽并导致扭曲序列化。这样做可能会对性能产生很大的负面影响。

【讨论】:

  • 在我的例子中,对 int4 值数组的访问不是随机的。它将使用固定索引访问:array[0]、array[1] 等,因此不应序列化内存事务。
  • 我对NVCC生成的PTX文件拍了很多,看来你的开关状态确实会产生分支。
  • 我已更新问题以包含生成的 PTX 程序集。但是,我的程序似乎确实有效。它利用了warp-synchronicity,所以这些分支并没有破坏程序是相当令人困惑的。
  • 我确实警告过你我没有编译它。如果访问不是随机的并且每个索引在编译时都是已知的,则考虑将 index 参数设置为 fetch4 模板参数。这将允许编译器优化分支。
  • 对常量内存的访问不是随机的,而是将向量读入本地内存,根据线程 ID 访问 32 位字。我在想也许有一种更简洁的方法可以做到这一点,但我应该先运行一个配置文件,看看这种新方法与我以前版本的程序相比如何。如果您仍然感兴趣,我会更新您。
猜你喜欢
  • 2017-12-21
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 2019-11-16
  • 1970-01-01
相关资源
最近更新 更多