【问题标题】:Optimise byte operations CUDA优化字节操作 CUDA
【发布时间】:2014-11-14 01:02:16
【问题描述】:

我对 Cuda 比较陌生,我正在尝试编写一个内核来计算查询向量和大型向量数据库之间的绝对差异之和。两者的元素必须是 8 位无符号整数。我的内核基于 nvidias 示例并行缩减内核,我也读过这个thread

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

我的数据集由 1024 字节的查询向量、100,000 x 1024 字节的数据库组成

我有 128 个线程的 100,000 个块,如果每个块访问相同的 1024 字节 query_vector,那会导致性能变差吗?因为每个块都在访问相同的内存位置。

blockSize 和共享内存都设置为 128 和 128*sizeof(int),128 是 #define'd as 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 倍的带宽增加,显然它会导致错误的答案,但我相信至少有很大一部分时间在那里度过。

我访问字节的方式是否有可能造成银行冲突?如果可以,我可以避免冲突吗?

我对@9​​87654323@ 的用法正确吗?

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

我还能做哪些其他优化(我假设很多,因为我是个新手)?

谢谢

编辑:

我的机器规格如下:

Windows XP 2002 SP3

英特尔 6600 2.40GHz

2GB 内存

GT640 GDDR5 1gb

visual c++ 2010 express

【问题讨论】:

  • 您的BYTE 是如何定义的?
  • 它只是一个 uint8_t。
  • 没有代码我可以快速编译和运行,性能很难说太多。银行冲突不应成为此代码中的问题。您是否通过 NVIDIA 分析器运行代码?这通常可以让您很好地了解正在发生的事情。另外,我认为您在减少的第一步和经线范围的减少之间缺少一个 __syncthreads()。

标签: c++ optimization cuda byte absolute-value


【解决方案1】:

对于此类问题,最好提供一个完整代码,以便某人可以编译和运行,而无需添加任何内容或更改任何内容。一般来说,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 类型指针来引用共享数据。由于我们现在在 warp 中运行,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 是近似上限)。

您的问题:

我是否有可能以访问字节的方式造成银行冲突?如果是这样,我可以避免冲突吗?

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

我对 reinterpret_cast 的使用正确吗?

是的,它似乎是正确的(我下面的示例代码,以及上面提到的修复,验证你的内核产生的结果与一个幼稚的主机函数实现匹配。)

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

在这种情况下,正如@njuffa 指出的那样,SIMD intrinsics 可以通过一条指令处理这个问题(__vsadu4(),请参见下面的示例代码)。

我还能做哪些其他优化(我会假设很多,因为我是个新手)?

  1. 使用@MichalHosala 提出的cc3.0 warp-shuffle reduction 方法

  2. 利用 SIMD 固有的 __vsadu4() 来简化和改进 @njuffa 建议的字节数量的处理。

  3. 将数据库矢量数据重新组织到列优先存储中。这使我们可以放弃普通的并行归约方法(即使是在第 1 项中提到的方法)并切换到直接 for 循环读取内核,一个线程计算整个向量比较。这允许我们的内核在这种情况下达到大约设备的内存带宽(cc3.5 GT640)。

这里是代码和示例运行,显示了 3 个实现:您的原始实现(加上上面命名的“修复”以使其产生正确的结果),一个 opt1 内核,它修改您的以包含项目 1 和 2上面的列表,以及一个 opt2 内核,它使用上面列表中的 2 和 3 的方法替换您的内核。根据我的测量,您的内核达到了 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!
$

一些注意事项:

  1. 上面的代码,特别是你的内核,必须为cc3.0或更高版本编译,我设置测试用例的方式。这是因为我在单个 1D 网格中创建了 100,000 个块,因此我们无法在 cc2.0 设备上按原样运行。
  2. 通过修改网格和块参数,可以对 opt2 内核进行一些额外的微调,尤其是在不同设备上运行时。我将这些设置为 64 和 512,并且这些值不应该是关键的(尽管块应该是 VECTOR_SIZE/4 线程或更大),因为该算法使用网格跨步循环来覆盖整个向量集。 GT640 只有 2 个 SM,所以在这种情况下,64 个线程块足以让设备保持忙碌(甚至可能 32 个也可以)。您可能需要修改这些以在更大的设备上获得最佳性能。

【讨论】:

  • 谢谢,这很好,只是我计算的是查询向量和数据库中每个单独向量之间的绝对差异的单独总和,而不是所有这些向量的总和,这就是为什么我使用了 100,000 个块(实际上是 10*10000 的网格)。除非我解释你的代码错误。我通过将处理的字节数 (1024*100,000*2) 除以 nvidia 分析器测量的内核运行时间来衡量性能。
  • 有什么理由比__shfl_xor更偏向于减少易失性翘曲的方法?无论如何,代码必须为 cc3.0 编译,当我最近在写论文时,似乎 shfl 比 volatile 好一点点,而且安全性高了两点。所以只是好奇......
  • 对不起,我误会了。我还是很好奇你的时间。当我在我显示的代码上运行nvprof 时,我得到的时序数字与两个内核的基于cudaEvent 的时序相匹配,即 your 内核大约为 6.4 毫秒。使用你的算术(我不确定我是否同意,但没关系)我得到:(1024*100,000*2)/0.0064 = 32GB/s,而不是 5GB/s。无论如何,作为进一步的优化,您的数据库向量存储现在隐含地以行为主。是否可以将其切换为列主要存储?然后可以进行进一步的优化。
  • 是的,我看不出有什么理由不能切换到列专业,只要结果是正确的答案,我愿意接受任何建议。为什么你不同意我的算术?此外,我使用 cudaEvent 获得不同的时间,然后在 nvidia 分析工具中。
  • 我肯定不会接近 6.4 毫秒。主机硬件规格会对 GPU 性能产生影响吗?不幸的是,我被一台相当旧的 Windows XP 机器困住了。使用带宽实用工具 IIRC,我还得到了 32GB/秒之类的数据,而 nvidia sdk 缩减样本的运行速度约为 25GB/秒。
【解决方案2】:

一件事立刻引起了我的注意:

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

第一个条件在所有地方都为真,而第二个仅在前两个经线中。因此,您可以从将它们的顺序转换为:

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

这将允许除前两个外的所有经线更快地完成执行。

接下来 - 您可以使用 __shfl_xor 指令显着加快扭曲级别的减少:

/* reduce the final warp */
if ( threadIdx.x < 32 ) {
  auto localSum = sum[threadIdx.x] + sum[threadIdx.x + 32]); 
  for (auto i = 16; i >= 1; i /= 2)
  {
      localSum = localSum + __shfl_xor(localSum, i);
  }

  if (threadIdx.x == 0) result[db_linear_index] = localSum;
}

我不是说就是这样,您的代码没有更多问题,但这些问题我很容易发现。我什至没有使用我的解决方案测试过性能,但我相信它应该会有所改进。

编辑: 您似乎还不必要地向共享内存写入了四次:

/* 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 ); 

为什么不简单地执行以下操作?

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

【讨论】:

  • 我现在看到我的答案到目前为止还没有涵盖所提供代码的所有问题,但我现在无法正确写下来,可能明天再回复。
  • 谢谢,编译器如何解释将所有 abs() 放在一个语句中?此外,GT640 GDDR5 是一个计算 3.5 卡,根据:developer.nvidia.com/cuda-gpus
  • += 被定义为 T&amp; T::operator +=(const T2&amp; b); 所以我相信它首先简单地总结了 += 右边的所有参数来创建 b 然后它传递给 += 作为参数,因此它会导致对 sum 的单个赋值操作,而不是四个。啊,我错过了 GT640 有 GDDR3 和 GDDR5 版本的事实......我删除了与加速
  • 您可能想要研究使用直接对压缩字节进行操作的 SIMD 设备函数(内部函数),例如 __vabsdiffu4()__vsadu4()(对于无符号字节)或 __vabsdiffs4()__vsads4() (对于有符号字节)。有关详细信息,请参阅 CUDA 数学 API 文档:docs.nvidia.com/cuda/cuda-math-api/…
猜你喜欢
  • 2011-01-26
  • 2021-11-13
  • 1970-01-01
  • 2016-07-29
  • 2013-02-22
  • 2011-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多