CUDA - 固定内存与可分页内存的权衡

时间:2018-05-04 23:08:26

标签: memory optimization cuda malloc gpu

根据我的理解,当我们尝试将可分页内存从主机传输到设备时,cudamemcpy会自动将数据复制到固定内存(缓冲区),然后传输到设备。

许多代码优化建议涉及使用固定内存而不是可分页内存。我不明白它会更快。虽然传输本身会更快,因为它直接来自固定内存而不需要在传输之前复制,但您仍然必须将内容从可分页内存复制到固定内存,这会产生大量开销。我误解了这种情况吗?有人可以向我解释为什么使用固定内存会更快,因为复制会产生开销,而且事实上我们只是手动执行cudamemcpy可以自动执行的操作?

1 个答案:

答案 0 :(得分:1)

如果要重置复制和计算,则需要固定内存。

在某些情况下,固定内存也可能提供性能优势。如果我们可以重用用于在主机和设备之间传输数据的缓冲区,则通常会注意到这一点。

  

你仍然需要自己将内容从可分页内存复制到固定内存,这会产生很多开销。

我不认为你必须在可以想象的情况下将数据从可分页内存传输到固定内存。

根据您的交叉发布here上的对话框,我将提供以下工作示例,显示固定和非固定内存之间的比较:

$ cat t113.cu
#include <stdio.h>
#include <stdlib.h>

typedef double my_T;
const int ds = 1024;
const int num_iter = 100;
const int block_dim = 16;

// C = A * B
// naive!!
template <typename T>
__global__ void mm(const T * __restrict__ A, const T * __restrict__ B, T * __restrict__ C, size_t d)
{
  int idx = threadIdx.x+blockDim.x*blockIdx.x;
  int idy = threadIdx.y+blockDim.y*blockIdx.y;

  if ((idx < d) && (idy < d)){
    T temp = 0;
    for (int i = 0; i < d; i++)
      temp += A[idy*d + i]*B[i*d + idx];
    C[idy*d + idx] = temp;
    }
}

int main(int argc, char *argv[]){

  int use_pinned = 0;
  if (argc > 1) use_pinned = atoi(argv[1]);
  if (use_pinned) printf("Using pinned memory\n");
  else printf("Using pageable memory\n");
  my_T *d_A, *d_B, *d_C, *h_A, *h_B, *h_C;
  int bs = ds*ds*sizeof(my_T);
  cudaMalloc(&d_A, bs);
  cudaMalloc(&d_B, bs);
  cudaMalloc(&d_C, bs);
  if (use_pinned){
    cudaHostAlloc(&h_A, bs, cudaHostAllocDefault);
    cudaHostAlloc(&h_B, bs, cudaHostAllocDefault);
    cudaHostAlloc(&h_C, bs, cudaHostAllocDefault);}
  else {
    h_A = (my_T *)malloc(bs);
    h_B = (my_T *)malloc(bs);
    h_C = (my_T *)malloc(bs);}
  cudaMemset(d_A, 0, bs);
  cudaMemset(d_B, 0, bs);
  memset(h_C, 0, bs);
  dim3 block(block_dim,block_dim);
  dim3 grid((ds+block.x-1)/block.x, (ds+block.y-1)/block.y);
  for (int iter = 0; iter<num_iter; iter++){
    mm<<<grid, block>>>(d_A, d_B, d_C, ds);
    if (iter > 1) if (h_C[0] != (my_T)((iter-2)*(iter-2)*ds)) printf("validation failure at iteration %d, was %f, should be %f\n", iter, h_C[0], (my_T) ((iter-2)*(iter-2)*ds));
    for (int i = 0; i < ds*ds; i++) {h_A[i] = iter; h_B[i] = iter;}
    cudaMemcpy(h_C, d_C, bs, cudaMemcpyDeviceToHost);
    cudaMemcpy(d_A, h_A, bs, cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, h_B, bs, cudaMemcpyHostToDevice);}
  printf("%s\n", cudaGetErrorString(cudaGetLastError()));
}
$ nvcc -arch=sm_60 -o t113 t113.cu
$ time ./t113
Using pageable memory
no error

real    0m1.987s
user    0m1.414s
sys     0m0.571s
$ time ./t113 1
Using pinned memory
no error

real    0m1.487s
user    0m0.903s
sys     0m0.579s
$

CUDA 9.1,CentOS 7.4,Tesla P100

简单地说,这段代码正在做100&#34;天真&#34; GPU上的矩阵乘法运算。在每次迭代中,我们在GPU上启动矩阵乘法,并且在完成此操作时,我们正在更新主机(输入)数据。矩阵乘法完成后,我们将结果传输到主机,然后将新输入数据传输到设备,然后执行另一次迭代。

我并不是说这段代码是完美优化的。例如,内核是一个简单的实现(如果你想要一个快速矩阵乘法,你应该使用CUBLAS)。如果您认真对待优化,您可能希望将此示例中的数据传输与设备代码执行重叠。在这种情况下,无论如何都会被迫使用固定缓冲区。但是并不总是能够在每个应用程序中实现复制和计算的重叠,并且在某些情况下(例如提供的示例)使用固定缓冲区可以在性能方面提供帮助。

如果您坚持要与必须先将数据从非固定缓冲区复制到固定缓冲区的情况进行比较,那么可能没有任何好处。但是如果没有你想到的具体例子,对我来说并不明显,你只能使用固定主机缓冲区完成所有工作(对于你打算发送到GPU或从GPU发送的数据) )。如果从磁盘或网络读取数据,则可以将其读入固定缓冲区。如果您先进行一些主机计算,则可以使用固定缓冲区。然后将这些固定的缓冲区数据发送到GPU。