簡體   English   中英

如何使用CUDA中的uint4向量正確轉換全局內存陣列以增加內存吞吐量?

[英]How to properly cast a global memory array using the uint4 vector in CUDA to increase memory throughput?

通常,有兩種技術可提高計算能力1.3 GPU上CUDA內核上全局內存的內存吞吐量; 存儲器訪問合並並且訪問至少4個字節的字。 通過第一種技術,通過相同半彎曲的線程對相同內存段的訪問被合並為更少的事務,同時要訪問至少4個字節的字,該內存段實際上從32字節增加到128個字節。

更新:基於標准答案的解決方案 要在全局存儲器中存儲無符號字符時訪問16字節而不是1字節的字,通常通過將內存數組轉換為uint4來使用uint4向量。 要從uint4向量中獲取值,可以將其重鑄為uchar4,如下所示:

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

__global__ void kernel ( unsigned char *d_text, unsigned char *d_out ) {

    int idx = blockIdx.x * blockDim.x + threadIdx.x;

    extern __shared__ unsigned char s_array[];

    uint4 *uint4_text = reinterpret_cast<uint4 *>(d_text);
    uint4 uint4_var;

    //memory transaction
    uint4_var = uint4_text[0];

    //recast data to uchar4
    uchar4 c0 = *reinterpret_cast<uchar4 *>(&uint4_var.x);
    uchar4 c4 = *reinterpret_cast<uchar4 *>(&uint4_var.y);
    uchar4 c8 = *reinterpret_cast<uchar4 *>(&uint4_var.z);
    uchar4 c12 = *reinterpret_cast<uchar4 *>(&uint4_var.w);

    d_out[idx] = c0.y;
}

int main ( void ) {

    unsigned char *d_text, *d_out;

    unsigned char *h_out = ( unsigned char * ) malloc ( 16 * sizeof ( unsigned char ) );
    unsigned char *h_text = ( unsigned char * ) malloc ( 16 * sizeof ( unsigned char ) );

    int i;

    for ( i = 0; i < 16; i++ )
            h_text[i] = 65 + i;

    cudaMalloc ( ( void** ) &d_text, 16 * sizeof ( unsigned char ) );
    cudaMalloc ( ( void** ) &d_out, 16 * sizeof ( unsigned char ) );

    cudaMemcpy ( d_text, h_text, 16 * sizeof ( unsigned char ), cudaMemcpyHostToDevice );

    kernel<<<1,16>>>(d_text, d_out );

    cudaMemcpy ( h_out, d_out, 16 * sizeof ( unsigned char ), cudaMemcpyDeviceToHost );

    for ( i = 0; i < 16; i++ )
            printf("%c\n", h_out[i]);

    return 0;
}

強制轉換為char *可以正常工作。 你試過了嗎? 如果是這樣,發生了什么事提示了這個問題?

在您的示例中,您似乎可以將s_array轉換為int*並從var.x進行單個復制(將j乘以4而不是16)。

如果您需要更靈活地對單詞中的字節進行改組,則可以使用__byte_perm()內部函數。 例如,要反轉整數x字節的順序,可以執行__byte_perm(x, 0, 0x0123);

使用向量類型甚至單個int來存儲字節可能不會帶來任何好處。 在Fermi上,全局內存事務為128字節寬。 因此,當您的warp命中一條指令來執行從全局內存中加載/存儲的操作時,GPU將執行服務於32個線程所需的盡可能多的128字節事務。 性能將主要取決於需要執行多少個單獨的事務,而不取決於每個線程如何加載或存儲其字節。

如果我了解您要執行的操作,則邏輯方法是使用C ++ reinterpret_cast機制使編譯器生成正確的向量加載指令,然后使用內置於字節大小的向量類型uchar4的CUDA來訪問每個字節中的每個字節。從全局存儲器加載的四個32位字。 使用這種方法,您真的可以信任編譯器,因為它知道在每個32位寄存器中進行字節訪問的最佳方法。

一個完全人為的示例如下所示:

#include <cstdio>
#include <cstdlib>

__global__
void kernel(unsigned int *in, unsigned char* out)
{
    int tid = threadIdx.x;

    uint4* p = reinterpret_cast<uint4*>(in);
    uint4  i4 = p[tid]; // vector load here

    uchar4 c0 = *reinterpret_cast<uchar4 *>(&i4.x);
    uchar4 c4 = *reinterpret_cast<uchar4 *>(&i4.y);
    uchar4 c8 = *reinterpret_cast<uchar4 *>(&i4.z);
    uchar4 c12 = *reinterpret_cast<uchar4 *>(&i4.w);

    out[tid*4+0] = c0.x;
    out[tid*4+1] = c4.y;
    out[tid*4+2] = c8.z;
    out[tid*4+3] = c12.w;
}

int main(void)
{
    unsigned int c[8] = { 
        2021161062, 2021158776, 2020964472, 1920497784, 
        2021161058, 2021161336, 2020898936, 1702393976 };

    unsigned int * _c;
    cudaMalloc((void **)&_c, sizeof(int)*size_t(8));
    cudaMemcpy(_c, c, sizeof(int)*size_t(8), cudaMemcpyHostToDevice);
    unsigned char * _m;
    cudaMalloc((void **)&_m, sizeof(unsigned char)*size_t(8));

    kernel<<<1,2>>>(_c, _m);

    unsigned char m[8];
    cudaMemcpy(m, _m, sizeof(unsigned char)*size_t(8), cudaMemcpyDeviceToHost);

    for(int i=0; i<8; i++)
        fprintf(stdout, "%d %c\n", i, m[i]);

    return 0;
}

它應該產生一個嵌入在提供給內核的無符號整數數組中的可讀字符串。

一個警告是,用於計算1.x目標的open64編譯器如果可以檢測到並非向量中的所有單詞都已被實際使用,則通常會挫敗這種嘗試生成向量負載的策略。 因此,請確保觸摸輸入向量類型中的所有輸入單詞,以確保編譯器正常播放。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM