CUDA优化实例(二)对齐与合并

本文通过实验对比了不同访问模式下CUDA内存性能的变化,证实了现代GPU对于非对齐内存访问的优化效果,并展示了合并访问对性能的重要影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CUDA优化实例(二)对齐与合并

本篇主要通过反正的方式进行实验,即说明不合并的内存访问方式慢,以此来说明对全局内存的访问一定要保证合并。

引言

关于全局内存的对齐与合并问题,前面的文章1 前面的文章2也介绍了,我在做有关对齐的试验时发现许多不可解释的问题,主要是对齐的问题,我发现这与我在书中学的不一样,为此我去官方文档中寻找线索,发现,现在的GPU对非对齐问题都进行了优化,不管对齐不对齐,它们的性能几乎是一样的。之前实验中也得到了证明,接下来的实验仍可证明这一点。那么优化对齐在现代的GPU(如GTX1050Ti)中就没有什么必要了。那么合并呢?下面会实验证明,合并还是对性能有很大影响的。

实验

本实验参考的是CUDA官方网站中的例子官网例子

代码:

/* Copyright (c) 1993-2015, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <stdio.h>
#include <assert.h>

// Convenience function for checking CUDA runtime API results
// can be wrapped around any runtime API call. No-op in release builds.
inline
cudaError_t checkCuda(cudaError_t result)
{
#if defined(DEBUG) || defined(_DEBUG)
  if (result != cudaSuccess) {
    fprintf(stderr, "CUDA Runtime Error: %s\n", cudaGetErrorString(result));
    assert(result == cudaSuccess);
  }
#endif
  return result;
}

template <typename T>
__global__ void offset(T* a, int s)
{
  int i = blockDim.x * blockIdx.x + threadIdx.x + s;
  a[i] = a[i] + 1;
}

template <typename T>
__global__ void stride(T* a, int s)
{
  int i = (blockDim.x * blockIdx.x + threadIdx.x) * s;
  a[i] = a[i] + 1;
}


template <typename T>
void runTest(int deviceId, int nMB)
{
  int blockSize = 256;
  float ms;

  T *d_a;
  cudaEvent_t startEvent, stopEvent;

  int n = nMB*1024*1024/sizeof(T);

  // NB:  d_a(33*nMB) for stride case
  checkCuda( cudaMalloc(&d_a, n * 33 * sizeof(T)) );

  checkCuda( cudaEventCreate(&startEvent) );
  checkCuda( cudaEventCreate(&stopEvent) );

  printf("Offset, Bandwidth (GB/s):\n");

  offset<<<n/blockSize, blockSize>>>(d_a, 0); // warm up

  for (int i = 0; i <= 32; i++) {
    checkCuda( cudaMemset(d_a, 0, n * sizeof(T)) );

    checkCuda( cudaEventRecord(startEvent,0) );
    offset<<<n/blockSize, blockSize>>>(d_a, i);
    checkCuda( cudaEventRecord(stopEvent,0) );
    checkCuda( cudaEventSynchronize(stopEvent) );

    checkCuda( cudaEventElapsedTime(&ms, startEvent, stopEvent) );
    printf("%d, %f\n", i, 2*nMB/ms);
  }

  printf("\n");
  printf("Stride, Bandwidth (GB/s):\n");

  stride<<<n/blockSize, blockSize>>>(d_a, 1); // warm up
  for (int i = 1; i <= 32; i++) {
    checkCuda( cudaMemset(d_a, 0, n * sizeof(T)) );

    checkCuda( cudaEventRecord(startEvent,0) );
    stride<<<n/blockSize, blockSize>>>(d_a, i);
    checkCuda( cudaEventRecord(stopEvent,0) );
    checkCuda( cudaEventSynchronize(stopEvent) );

    checkCuda( cudaEventElapsedTime(&ms, startEvent, stopEvent) );
    printf("%d, %f\n", i, 2*nMB/ms);
  }

  stride32<<<n/blockSize, blockSize>>>(d_a, 2);


  checkCuda( cudaEventDestroy(startEvent) );
  checkCuda( cudaEventDestroy(stopEvent) );
  cudaFree(d_a);
}

int main(int argc, char **argv)
{
  int nMB = 4;
  int deviceId = 0;
  bool bFp64 = false;

  for (int i = 1; i < argc; i++) {    
    if (!strncmp(argv[i], "dev=", 4))
      deviceId = atoi((char*)(&argv[i][4]));
    else if (!strcmp(argv[i], "fp64"))
      bFp64 = true;
  }

  cudaDeviceProp prop;

  checkCuda( cudaSetDevice(deviceId) )
  ;
  checkCuda( cudaGetDeviceProperties(&prop, deviceId) );
  printf("Device: %s\n", prop.name);
  printf("Transfer size (MB): %d\n", nMB);

  printf("%s Precision\n", bFp64 ? "Double" : "Single");

  if (bFp64) runTest<double>(deviceId, nMB);
  else       runTest<float>(deviceId, nMB);
}

结果:

结果
结果2

分析:

1 数据是所有线程iD所能达到的33倍,即不会出现访问非法内存,
2 int i = (blockDim.x * blockIdx.x + threadIdx.x) * s; 表明访问的数据字节位置,成s倍变化。
3 最后耗时有最低耗时和最高耗时和平均耗时,因为核函数访问了32次

结论

分析发现,第一个核函数是针对齐问题的,第二个核函数是针对合并问题的。
第一个核函数的32次非对齐的情况的性能基本一样,验证了我前面所说的现代GPU对对齐问题的内部优化。第二个核函数随着步长的增大,内存请求慢慢的不在同一个内存事物中,带宽自然就降低了。但其降低不是我想的50%,25%。。。而是最低效率是12.5%,这也说明了,这与书上和猜想的不同,即全局内存访问的方式被优化了,不再是我们认为的那样,对齐访问不再影响性能,合并访问的性能下降的梯度也有所减缓,所以不能在向书上那样认识现代GPU的内存访问方式了,不过,有一点还是没变的,即提高内存访问效率,shared memory可让全局内存成合并访问,成为优化CUDA的最有力武器。

GPU高性能计算系列丛书的第一本《GPU高性能计算之CUDA》已经出版,由张舒,褚艳利,赵开勇,张钰勃所编写。本书除了详细介绍了CUDA的软硬件架构以及C for CUDA程序开发和优化的策略外,还包含有大量的实例供读者学习参考用。 下表是各个实例的介绍列表。 文件夹 对应书中章节 备注 ACsearch_DPPcompact_with_driver 5.2.2 AC多模式匹配算法 asyncAPI 2.5 异步API调用示例 bandwidthTest 2.3.6 带宽测试 Bitonic 5.1.1 双调排序网络 conjugateGradient 5.2.1 共轭梯度算法,CUBLAS实现 cudaMPI 2.7.3 CUDA+MPI管理GPU集群 cudaOpenMP 2.7.2 CUDA+OpenMP管理多GPU deviceQuery 2.1.4 设备查询 histKernel 2.4.3 亮度直方图统计 matrixAssign 2.1.4 矩阵赋值 matrixMul 4.7.1 矩阵乘法,利用shared memory matrixMul_Berkeley 4.7.1 矩阵乘法,利用register reduction 4.7.2 并行归约(缩减)程序 scan 5.1.2 Scan算法,例如计算前缀和 scanLargeArray 5.1.2 Scan算法,可以处理大数组 simpleCUBLAS 5.1.3 CUBLAS库的简单应用 simpleCUFFT 5.1.4 CUFFT库的简单应用 simpleD3D9 2.6.2 CUDADirect3D 9互操作 simpleD3D10 2.6.2 CUDADirect3D10互操作 simpleGL 2.6.1 CUDAOpenGL互操作 simpleMultiGPU 2.7.1 多设备控制 simpleStreams 2.5.2 流的使用演示 simpleTexture 2.3.8 简单的纹理使用 simpleTextureDrv 2.3.8 简单的纹理使用,驱动API 实现 sortingNetworks 5.1.1 双调排序网络,处理大数组 threadMigration 2.7.1 通过上下文管理和设备管理功能实现多设备并行计算 timing 4.2.1 设备端测时 transpose 4.7.3 矩阵转置 transposeDiagonal 4.7.3 矩阵转置,考虑partition conflict VectorAdd 2.2.3/2.3.4 矢量加 VectorAddDrv 2.2.3/2.3.4 矢量加,驱动API实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值