假设我定义了一些GPU可见的数组:
double* doubleArr = createCUDADouble(fieldLen);
float* floatArr = createCUDAFloat(fieldLen);
char* charArr = createCUDAChar(fieldLen);
现在,我有以下CUDA线程:
void thread(){
int o = getOffset(); // the same for all threads in launch
double d = doubleArr[threadIdx.x + o];
float f = floatArr[threadIdx.x + o];
char c = charArr[threadIdx.x + o];
}
我不太确定我是否正确地解释了文档,它对我的设计非常关键:对double,float和char的内存访问是否会很好地合并?(猜猜:是的,它将适合sizeof(type) * blockSize.x / (transaction size)
个交易,加上上边界和下边界可能还有一个额外的交易。)
答案 0 :(得分:2)
是的,对于您展示的所有案例,假设createCUDAxxxxx
转换为某种普通cudaMalloc
类型的操作,一切都应该很好地合并。
如果我们有通过cudaMalloc
分配的普通1D设备数组,一般来说,如果我们的加载模式包含表单的数组索引,我们应该在线程之间有良好的合并行为:
data_array[some_constant + threadIdx.x];
数组的数据类型无关紧要 - 它会很好地合并。
但是,从性能角度来看,全局负载(假设L1未命中)将以最小128字节的粒度发生。因此,为每个帖子加载较大的尺寸(例如int
,float
,double
,float4
等)可能会提供更好的性能。如果负载跨越足够多的经线,则缓存往往会减轻任何差异。
使用profiler对特定代码进行验证也很容易。根据您选择的探查器,有很多方法可以做到这一点,例如使用nvprof,您可以这样做:
nvprof --metric gld_efficiency ./my_exe
它将返回一个平均百分比数字,或多或少准确地反映了全局负载上发生的最佳合并的百分比。
This是我经常引用的有关内存优化的其他背景信息的演示文稿。
我想有人会来并注意到这种模式:
data_array[some_constant + threadIdx.x];
大致对应于上述演示文稿的幻灯片40-41上所示的访问类型。并且 aha !! 效率降至50%-80%。如果只考虑单个扭曲载荷,那就是这样。但是,参考幻灯片40,我们看到“第一个”加载将需要加载两个高速缓存行。然而,之后,为了简单起见,额外的负载(向右移动)每个warp-load只需要一个额外的/新的高速缓存行(假设存在L1或L2高速缓存,并且合理的位置,即没有捶打)。因此,在一个相当大的数组(超过128个字节)上,平均值要求将是每个warp一个新的高速缓存行,这相当于100%的效率。