gpt4 book ai didi

c++ - 优化字节操作CUDA

转载 作者:太空宇宙 更新时间:2023-11-03 10:28:20 25 4
gpt4 key购买 nike

我对Cuda还是比较陌生,我正在尝试编写一个内核,该内核计算查询 vector 和 vector 的大型数据库之间的绝对差之和。两者的元素都必须是8位无符号整数。我的内核基于nvidias示例并行约简内核,我也阅读了thread

我只得到大约5GB / s的速度,这并不比快速CPU好多少,甚至还不能接近我的DDR5 GT640的理论带宽(约80GB / s)。

我的数据集包含1024字节查询 vector ,100,000 x 1024字节数据库

我有100,000个128个线程的块,如果每个块访问相同的1024字节query_vector,那会导致性能下降吗?由于每个块都访问相同的存储位置。

blockSize和共享内存都设置为128和128 * sizeof(int),将128定义为THREADS_PER_BLOCK

template<UINT blockSize> __global__ void reduction_sum_abs( BYTE* query_vector, BYTE* db_vector, uint32_t* result )
{
extern __shared__ UINT sum[];
UINT db_linear_index = (blockIdx.y*gridDim.x) + blockIdx.x ;
UINT i = threadIdx.x;

sum[threadIdx.x] = 0;

int* p_q_int = reinterpret_cast<int*>(query_vector);
int* p_db_int = reinterpret_cast<int*>(db_vector);

while( i < VECTOR_SIZE/4 ) {

/* memory transaction */
int q_int = p_q_int[i];
int db_int = p_db_int[db_linear_index*VECTOR_SIZE/4 + i];

uchar4 a0 = *reinterpret_cast<uchar4*>(&q_int);
uchar4 b0 = *reinterpret_cast<uchar4*>(&db_int);

/* sum of absolute difference */
sum[threadIdx.x] += abs( (int)a0.x - b0.x );
sum[threadIdx.x] += abs( (int)a0.y - b0.y );
sum[threadIdx.x] += abs( (int)a0.z - b0.z );
sum[threadIdx.x] += abs( (int)a0.w - b0.w );

i += THREADS_PER_BLOCK;

}

__syncthreads();

if ( blockSize >= 128 ) {
if ( threadIdx.x < 64 ) {
sum[threadIdx.x] += sum[threadIdx.x + 64];
}
}

/* reduce the final warp */
if ( threadIdx.x < 32 ) {
if ( blockSize >= 64 ) { sum[threadIdx.x] += sum[threadIdx.x + 32]; } __syncthreads();

if ( blockSize >= 32 ) { sum[threadIdx.x] += sum[threadIdx.x + 16]; } __syncthreads();

if ( blockSize >= 16 ) { sum[threadIdx.x] += sum[threadIdx.x + 8 ]; } __syncthreads();

if ( blockSize >= 8 ) { sum[threadIdx.x] += sum[threadIdx.x + 4 ]; } __syncthreads();

if ( blockSize >= 4 ) { sum[threadIdx.x] += sum[threadIdx.x + 2 ]; } __syncthreads();

if ( blockSize >= 2 ) { sum[threadIdx.x] += sum[threadIdx.x + 1 ]; } __syncthreads();

}


/* copy the sum back to global */
if ( threadIdx.x == 0 ) {
result[db_linear_index] = sum[0];
}
}

如果我用注释掉实际绝对差计算的4行代码运行内核,我可以获得大约4倍的带宽增加,这显然会导致错误的答案,但是我相信至少有相当一部分时间是花在那里。

我是否可能以访问字节的方式创建存储区冲突?如果可以,我可以避免冲突吗?

我对reinterpret_cast的用法正确吗?

是否有更好的方法进行8位无符号计算?

我还能进行其他哪些优化(因为我是一个完整的新手,所以我会假设很多)?

谢谢

编辑:

我的机器规格如下:

Windows XP 2002 SP3

英特尔6600 2.40GHz

2GB内存

GT640 GDDR5 1GB

Visual C++ 2010 Express

最佳答案

此类问题的好习惯是提供一个完整的代码,使他人可以编译和运行该代码,而无需添加任何内容或更改任何内容。一般来说,SO期望this。由于您的问题也与性能有关,因此您还应该在完整的代码中包括实际的时序测量方法。

修复错误:

您的代码中至少有2个错误,其中一个@Jez已经指出。在此“部分减少”步骤之后:

if ( blockSize >= 128 ) {
if ( threadIdx.x < 64 ) {
sum[threadIdx.x] += sum[threadIdx.x + 64];
}
}

在进行其余操作之前,我们需要一个 __syncthreads();。通过以上更改,我能够使您的内核产生与我的幼稚主机实现相匹配的可重复结果。另外,由于您有类似这样的条件代码,因此在整个线程块中的计算结果都不相同:
if ( threadIdx.x < 32 ) {  

it is illegal在条件代码块内具有 __syncthreads()语句:
  if ( blockSize >= 64 ) { sum[threadIdx.x] += sum[threadIdx.x + 32]; } __syncthreads(); 

(同样,后续行也做同样的事情)。因此,建议修复此问题。有几种方法可以解决此问题,其中一种方法是切换为使用 volatile类型的指针来引用共享数据。由于我们现在在扭曲中进行操作,因此 volatile限定符会强制编译器执行我们想要的操作:
volatile UINT *vsum = sum;
if ( threadIdx.x < 32 ) {
if ( blockSize >= 64 ) vsum[threadIdx.x] += vsum[threadIdx.x + 32];
if ( blockSize >= 32 ) vsum[threadIdx.x] += vsum[threadIdx.x + 16];
if ( blockSize >= 16 ) vsum[threadIdx.x] += vsum[threadIdx.x + 8 ];
if ( blockSize >= 8 ) vsum[threadIdx.x] += vsum[threadIdx.x + 4 ];
if ( blockSize >= 4 ) vsum[threadIdx.x] += vsum[threadIdx.x + 2 ];
if ( blockSize >= 2 ) vsum[threadIdx.x] += vsum[threadIdx.x + 1 ];
}

CUDA parallel reduction sample codeassociated pdf对您来说可能是一个不错的评论。

时序/性能分析:

我碰巧有一个GT 640,cc3.5设备。当我在其上运行 bandwidthTest时,我发现设备之间的传输速度约为32GB / s。当设备内核访问设备内存时,该数字表示可实现带宽的合理近似上限。另外,当我添加基于 cudaEvent的时序并使用模拟数据围绕显示的内容构建示例代码时,我观察到吞吐量约为16GB / s,而不是5GB / s。因此,您的实际测量技术在这里将是有用的信息(实际上,可能需要完整的代码来分析我的内核时序与您的时序之间的差异)。

那么,问题仍然存在吗? (假设〜32GB / s是大约上限)。

你的问题:

Is it possible that I am creating bank conflicts the way I am accessing the bytes? if so can I avoid conflicts?



由于您的内核实际上有效地将字节作为32位量( uchar4)有效地加载,并且每个线程都在加载相邻的连续32位量,因此我认为内核不存在任何银行冲突访问问题。

Is my usage of reinterpret_cast correct?



是的,它似乎是正确的(我下面的示例代码以及上面提到的修复程序,验证了您的内核产生的结果是否与幼稚的宿主函数实现相匹配。)

Is there a better method for doing 8 bit unsigned calculations?



在这种情况下,如@njuffa所指出的,事实证明 SIMD intrinsics可以用一条指令( __vsadu4(),请参见下面的示例代码)来处理。

What other (I would assume many, as I'm a complete novice) optimisations can I make?


  • 使用@MichalHosala提出的cc3.0减少扭曲变形方法
  • 使用@njuffa提出的SIMD内部__vsadu4()简化和改进对字节数量的处理。
  • 将您的数据库 vector 数据重组为主要列存储。这使我们可以省去普通的并行约简方法(即使是第1条中提到的方法),也可以切换到直接for循环读取内核,一个线程计算整个 vector 比较。在这种情况下,这使我们的内核大约可以达到设备的内存带宽(cc3.5 GT640)。

  • 这是代码和示例运行,显示了3种实现:您的原始实现(加上上面命名的“修复”以获取正确的结果),一个opt1内核,将您的opt1内核修改为包含上面列表中的项目1和2,以及一个opt2内核,该内核使用上面列表中的2和3替代了您的opt2内核。根据我的测量,您的内核达到了16GB / s的速度,大约是GT640带宽的一半,opt1内核的运行速度约为24GB / s(增加的部分来自上述项目1和2),而opt2内核数据重组后,其运行速度约为全带宽(36GB / s)。
    $ cat t574.cu
    #include <stdio.h>
    #include <stdlib.h>
    #define THREADS_PER_BLOCK 128
    #define VECTOR_SIZE 1024
    #define NUM_DB_VEC 100000

    typedef unsigned char BYTE;
    typedef unsigned int UINT;
    typedef unsigned int uint32_t;


    template<UINT blockSize> __global__ void reduction_sum_abs( BYTE* query_vector, BYTE* db_vector, uint32_t* result )
    {
    extern __shared__ UINT sum[];
    UINT db_linear_index = (blockIdx.y*gridDim.x) + blockIdx.x ;
    UINT i = threadIdx.x;

    sum[threadIdx.x] = 0;

    int* p_q_int = reinterpret_cast<int*>(query_vector);
    int* p_db_int = reinterpret_cast<int*>(db_vector);

    while( i < VECTOR_SIZE/4 ) {

    /* memory transaction */
    int q_int = p_q_int[i];
    int db_int = p_db_int[db_linear_index*VECTOR_SIZE/4 + i];

    uchar4 a0 = *reinterpret_cast<uchar4*>(&q_int);
    uchar4 b0 = *reinterpret_cast<uchar4*>(&db_int);

    /* sum of absolute difference */
    sum[threadIdx.x] += abs( (int)a0.x - b0.x );
    sum[threadIdx.x] += abs( (int)a0.y - b0.y );
    sum[threadIdx.x] += abs( (int)a0.z - b0.z );
    sum[threadIdx.x] += abs( (int)a0.w - b0.w );

    i += THREADS_PER_BLOCK;

    }

    __syncthreads();

    if ( blockSize >= 128 ) {
    if ( threadIdx.x < 64 ) {
    sum[threadIdx.x] += sum[threadIdx.x + 64];
    }
    }
    __syncthreads(); // **
    /* reduce the final warp */
    if ( threadIdx.x < 32 ) {
    if ( blockSize >= 64 ) { sum[threadIdx.x] += sum[threadIdx.x + 32]; } __syncthreads();

    if ( blockSize >= 32 ) { sum[threadIdx.x] += sum[threadIdx.x + 16]; } __syncthreads();

    if ( blockSize >= 16 ) { sum[threadIdx.x] += sum[threadIdx.x + 8 ]; } __syncthreads();

    if ( blockSize >= 8 ) { sum[threadIdx.x] += sum[threadIdx.x + 4 ]; } __syncthreads();

    if ( blockSize >= 4 ) { sum[threadIdx.x] += sum[threadIdx.x + 2 ]; } __syncthreads();

    if ( blockSize >= 2 ) { sum[threadIdx.x] += sum[threadIdx.x + 1 ]; } __syncthreads();

    }


    /* copy the sum back to global */
    if ( threadIdx.x == 0 ) {
    result[db_linear_index] = sum[0];
    }
    }

    __global__ void reduction_sum_abs_opt1( BYTE* query_vector, BYTE* db_vector, uint32_t* result )
    {
    __shared__ UINT sum[THREADS_PER_BLOCK];
    UINT db_linear_index = (blockIdx.y*gridDim.x) + blockIdx.x ;
    UINT i = threadIdx.x;

    sum[threadIdx.x] = 0;

    UINT* p_q_int = reinterpret_cast<UINT*>(query_vector);
    UINT* p_db_int = reinterpret_cast<UINT*>(db_vector);

    while( i < VECTOR_SIZE/4 ) {

    /* memory transaction */
    UINT q_int = p_q_int[i];
    UINT db_int = p_db_int[db_linear_index*VECTOR_SIZE/4 + i];
    sum[threadIdx.x] += __vsadu4(q_int, db_int);

    i += THREADS_PER_BLOCK;

    }
    __syncthreads();
    // this reduction assumes THREADS_PER_BLOCK = 128
    if (threadIdx.x < 64) sum[threadIdx.x] += sum[threadIdx.x+64];
    __syncthreads();

    if ( threadIdx.x < 32 ) {
    unsigned localSum = sum[threadIdx.x] + sum[threadIdx.x + 32];
    for (int i = 16; i >= 1; i /= 2)
    localSum = localSum + __shfl_xor(localSum, i);
    if (threadIdx.x == 0) result[db_linear_index] = localSum;
    }
    }

    __global__ void reduction_sum_abs_opt2( BYTE* query_vector, UINT* db_vector_cm, uint32_t* result)
    {
    __shared__ UINT qv[VECTOR_SIZE/4];
    if (threadIdx.x < VECTOR_SIZE/4) qv[threadIdx.x] = *(reinterpret_cast<UINT *>(query_vector) + threadIdx.x);
    __syncthreads();
    int idx = threadIdx.x + blockDim.x*blockIdx.x;
    while (idx < NUM_DB_VEC){
    UINT sum = 0;
    for (int i = 0; i < VECTOR_SIZE/4; i++)
    sum += __vsadu4(qv[i], db_vector_cm[(i*NUM_DB_VEC)+idx]);
    result[idx] = sum;
    idx += gridDim.x*blockDim.x;}
    }

    unsigned long compute_host_result(BYTE *qvec, BYTE *db_vec){

    unsigned long temp = 0;
    for (int i =0; i < NUM_DB_VEC; i++)
    for (int j = 0; j < VECTOR_SIZE; j++)
    temp += (unsigned long) abs((int)qvec[j] - (int)db_vec[(i*VECTOR_SIZE)+j]);
    return temp;
    }

    int main(){

    float et;
    cudaEvent_t start, stop;
    BYTE *h_qvec, *d_qvec, *h_db_vec, *d_db_vec;
    uint32_t *h_res, *d_res;
    h_qvec = (BYTE *)malloc(VECTOR_SIZE*sizeof(BYTE));
    h_db_vec = (BYTE *)malloc(VECTOR_SIZE*NUM_DB_VEC*sizeof(BYTE));
    h_res = (uint32_t *)malloc(NUM_DB_VEC*sizeof(uint32_t));
    for (int i = 0; i < VECTOR_SIZE; i++){
    h_qvec[i] = rand()%256;
    for (int j = 0; j < NUM_DB_VEC; j++) h_db_vec[(j*VECTOR_SIZE)+i] = rand()%256;}
    cudaMalloc(&d_qvec, VECTOR_SIZE*sizeof(BYTE));
    cudaMalloc(&d_db_vec, VECTOR_SIZE*NUM_DB_VEC*sizeof(BYTE));
    cudaMalloc(&d_res, NUM_DB_VEC*sizeof(uint32_t));
    cudaMemcpy(d_qvec, h_qvec, VECTOR_SIZE*sizeof(BYTE), cudaMemcpyHostToDevice);
    cudaMemcpy(d_db_vec, h_db_vec, VECTOR_SIZE*NUM_DB_VEC*sizeof(BYTE), cudaMemcpyHostToDevice);
    cudaEventCreate(&start); cudaEventCreate(&stop);

    // initial run

    cudaMemset(d_res, 0, NUM_DB_VEC*sizeof(uint32_t));
    cudaEventRecord(start);
    reduction_sum_abs<THREADS_PER_BLOCK><<<NUM_DB_VEC, THREADS_PER_BLOCK, THREADS_PER_BLOCK*sizeof(int)>>>(d_qvec, d_db_vec, d_res);
    cudaEventRecord(stop);
    cudaDeviceSynchronize();
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&et, start, stop);
    cudaMemcpy(h_res, d_res, NUM_DB_VEC*sizeof(uint32_t), cudaMemcpyDeviceToHost);
    unsigned long h_result = 0;
    for (int i = 0; i < NUM_DB_VEC; i++) h_result += h_res[i];
    printf("1: et: %.2fms, bw: %.2fGB/s\n", et, (NUM_DB_VEC*VECTOR_SIZE)/(et*1000000));
    if (h_result == compute_host_result(h_qvec, h_db_vec)) printf("Success!\n");
    else printf("1: mismatch!\n");

    // optimized kernel 1
    cudaMemset(d_res, 0, NUM_DB_VEC*sizeof(uint32_t));
    cudaEventRecord(start);
    reduction_sum_abs_opt1<<<NUM_DB_VEC, THREADS_PER_BLOCK>>>(d_qvec, d_db_vec, d_res);
    cudaEventRecord(stop);
    cudaDeviceSynchronize();
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&et, start, stop);
    cudaMemcpy(h_res, d_res, NUM_DB_VEC*sizeof(uint32_t), cudaMemcpyDeviceToHost);
    h_result = 0;
    for (int i = 0; i < NUM_DB_VEC; i++) h_result += h_res[i];
    printf("2: et: %.2fms, bw: %.2fGB/s\n", et, (NUM_DB_VEC*VECTOR_SIZE)/(et*1000000));
    if(h_result == compute_host_result(h_qvec, h_db_vec)) printf("Success!\n");
    else printf("2: mismatch!\n");

    // convert db_vec to column-major storage for optimized kernel 2

    UINT *h_db_vec_cm, *d_db_vec_cm;
    h_db_vec_cm = (UINT *)malloc(NUM_DB_VEC*(VECTOR_SIZE/4)*sizeof(UINT));
    cudaMalloc(&d_db_vec_cm, NUM_DB_VEC*(VECTOR_SIZE/4)*sizeof(UINT));
    for (int i = 0; i < NUM_DB_VEC; i++)
    for (int j = 0; j < VECTOR_SIZE/4; j++)
    h_db_vec_cm[(j*NUM_DB_VEC)+i] = *(reinterpret_cast<UINT *>(h_db_vec + (i*VECTOR_SIZE))+j);
    cudaMemcpy(d_db_vec_cm, h_db_vec_cm, NUM_DB_VEC*(VECTOR_SIZE/4)*sizeof(UINT), cudaMemcpyHostToDevice);
    cudaMemset(d_res, 0, NUM_DB_VEC*sizeof(uint32_t));
    cudaEventRecord(start);
    reduction_sum_abs_opt2<<<64, 512>>>(d_qvec, d_db_vec_cm, d_res);
    cudaEventRecord(stop);
    cudaDeviceSynchronize();
    cudaEventSynchronize(stop);
    cudaEventElapsedTime(&et, start, stop);
    cudaMemcpy(h_res, d_res, NUM_DB_VEC*sizeof(uint32_t), cudaMemcpyDeviceToHost);
    h_result = 0;
    for (int i = 0; i < NUM_DB_VEC; i++) h_result += h_res[i];
    printf("3: et: %.2fms, bw: %.2fGB/s\n", et, (NUM_DB_VEC*VECTOR_SIZE)/(et*1000000));
    if(h_result == compute_host_result(h_qvec, h_db_vec)) printf("Success!\n");
    else printf("3: mismatch!\n");

    return 0;
    }

    $ nvcc -O3 -arch=sm_35 -o t574 t574.cu
    $ ./run35 t574
    1: et: 6.34ms, bw: 16.14GB/s
    Success!
    2: et: 4.16ms, bw: 24.61GB/s
    Success!
    3: et: 2.83ms, bw: 36.19GB/s
    Success!
    $

    一些注意事项:
  • 上面的代码(尤其是您的内核)必须针对cc3.0或更高版本进行编译,这是我设置测试用例的方式。这是因为我要在一个1D网格中创建100,000个块,因此,例如,我们不能在cc2.0设备上按原样运行它。
  • THere可能是一些额外的细微调整,尤其是在opt2内核上,通过修改grid和block参数,尤其是在不同设备上运行时。我将这些值设置为64和512,并且这些值应该不是关键的(尽管block应该是VECTOR_SIZE / 4线程或更大),因为该算法使用网格跨越循环来覆盖整个 vector 集。 GT640只有2个SM,因此在这种情况下,64个线程块足以使设备忙碌(甚至32个也可以)。您可能需要修改这些设置,以在更大的设备上获得最佳性能。
  • 关于c++ - 优化字节操作CUDA,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25933829/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com