簡體   English   中英

我應該如何以及何時使用cuda API使用傾斜指針?

[英]How and when should I use pitched pointer with the cuda API?

我對如何使用cudaMalloc()cudaMemcpy()分配和復制線性內存有很好的理解。 但是,當我想使用CUDA函數來分配和復制2D或3D矩陣時,我常常會被各種參數所迷惑,特別是關於在處理2D / 3D數組時總是存在的傾斜指針。 文檔很適合提供一些如何使用它們的例子,但它假設我熟悉填充和音高的概念,我不是。

我通常最終會調整我在文檔中或網絡上的其他地方找到的各種示例,但后面的盲目調試非常痛苦,所以我的問題是:

什么是球場? 我該如何使用它? 如何在CUDA中分配和復制2D和3D陣列?

這是關於CUDA中的傾斜指針和填充的解釋。

線性內存與填充內存

首先,讓我們從存在非線性內存的原因入手。 使用cudaMalloc分配內存時,結果就像使用malloc的分配一樣,我們有一個指定大小的連續內存塊,我們可以在其中放入任何我們想要的東西。 如果我們想要分配10000浮點數的向量,我們只需:

float* myVector;
cudaMalloc(&myVector, 10000*sizeof(float));

然后通過經典索引訪問myVector的第i個元素:

float element = myVector[i];

如果我們想要訪問下一個元素,我們只需:

float next_element = myvector[i+1];

它工作得非常好,因為訪問第一個元素旁邊的元素(因為我不知道並且我不希望現在的原因)便宜。

當我們將內存用作2D數組時,情況會有所不同。 假設我們的10000浮點矢量實際上是一個100x100陣列。 我們可以使用相同的cudaMalloc函數來分配它,如果我們想要讀取第i行,我們可以:

float* myArray;
cudaMalloc(&myArray, 10000*sizeof(float));
int row[100];  // number of columns
for (int j=0; j<100; ++j)
    row[j] = myArray[i*100+j];

單詞對齊

所以我們必須從myArray + 100 * i讀取內存到myArray + 101 * i-1。 它將占用的內存訪問操作數取決於此行占用的內存字數。 存儲器字中的字節數取決於實現。 為了在讀取單行時最小化內存訪問次數,我們必須確保在一個字的開頭開始行,因此我們必須為每一行填充內存,直到新行開始。

銀行沖突

填充數組的另一個原因是CUDA中的銀行機制,涉及共享內存訪問。 當陣列在共享存儲器中時,它被分成幾個存儲體。 兩個CUDA線程可以同時訪問它,前提是它們不訪問屬於同一存儲體的存儲器。 由於我們通常希望並行處理每一行,因此我們可以確保通過將每一行填充到新銀行的開頭來模擬訪問它。

現在,我們將使用cudaMallocPitch,而不是使用cudaMalloc分配2D數組:

size_t pitch;
float* myArray;
cudaMallocPitch(&myArray, &pitch, 100*sizeof(float), 100);  // width in bytes by height

請注意,此處的音高是函數的返回值:cudaMallocPitch檢查系統應該是什么,並返回適當的值。 cudaMallocPitch的作用如下:

  1. 分配第一行。
  2. 檢查分配的字節數是否正確對齊。 例如,它是128的倍數。
  3. 如果沒有,則分配更多字節以達到128的下一個倍數。 然后,音高是為單個行分配的字節數,包括額外字節(填充字節)。
  4. 重申每一行。

最后,我們通常分配的內存超過了必要的內存,因為每一行現在都是音高的大小,而不是w*sizeof(float)的大小。

但是現在,當我們想要訪問列中的元素時,我們必須這樣做:

float* row_start = (float*)((char*)myArray + row * pitch);
float column_element = row_start[column];

兩個連續列之間的字節偏移量不能再從數組的大小中推斷出來,這就是為什么我們要保持cudaMallocPitch返回的音高。 並且由於音高是填充大小的倍數(通常是字大小和行大小最大),因此效果很好。 好極了。

將數據復制到內存或從內存中復制數據

既然我們知道如何創建和訪問由cudaMallocPitch創建的數組中的單個元素,我們可能希望將其整個部分復制到其他內存中,也可以復制到其他內存中。

讓我們說我們想要在我們的主機上使用malloc分配的100x100數組中復制我們的數組:

float* host_memory = (float*)malloc(100*100*sizeof(float));

如果我們使用cudaMemcpy,我們將復制用cudaMallocPitch分配的所有內存,包括每行之間的填充字節。 我們必須做的是避免填充內存是逐個復制每一行。 我們可以手動完成:

for (size_t i=0; i<100; ++i) {
  cudaMemcpy(host_memory[i*100], myArray[pitch*i],
             100*sizeof(float), cudaMemcpyDeviceToHost);
}

或者,我們可以告訴CUDA API,我們只想從我們與它的方便填充字節分配的內存有用的內存中,因此如果能與自己惹自動處理這將是非常好的事實上,謝謝。 這里輸入cudaMemcpy2D:

cudaMemcpy2D(host_memory, 100*sizeof(float)/*no pitch on host*/,
             myArray, pitch/*CUDA pitch*/,
             100*sizeof(float)/*width in bytes*/, 100/*heigth*/, 
             cudaMemcpyDeviceToHost);

現在副本將自動完成。 它將復制寬度指定的字節數(此處:100xsizeof(float)),高度時間(此處為100),每次跳轉到下一行時跳過節距字節。 請注意,我們仍然必須提供目標內存的音高,因為它也可以填充。 這里不是,所以音高等於非填充數組的音高:它是一行的大小。 另請注意,memcpy函數中的width參數以字節表示,但height參數以元素數表示。 這是因為副本的完成方式,就像我上面寫的手冊一樣:寬度是一行中每個副本的大小(內存中連續的元素),高度是此操作必須的次數完成。 (作為一名物理學家,這些單位的不一致讓我非常惱火。)

處理3D陣列

3D陣列實際上與2D陣列沒有什么不同,不包括額外的填充。 3D數組只是填充行的2D 經典數組。 這就是為什么在分配3D數組時,您只得到一個音高,即沿着一行連續點之間的字節數差異。 如果要訪問沿深度維度的連續點,可以安全地將音高乘以列數,從而為slicePitch提供。

用於訪問3D內存的CUDA API與用於2D內存的CUDA API略有不同,但想法是相同的:

  • 使用cudaMalloc3D時,您會收到一個音高值,您必須小心保留該值以便隨后訪問內存。
  • 復制3D內存塊時,除非復制單行,否則不能使用cudaMemcpy。 您必須使用CUDA實用程序提供的任何其他類型的復制實用程序。
  • 將數據復制到線性存儲器或從線性存儲器復制數據時,即使它與指針無關,也必須為指針提供間距:此間距是行的大小,以字節表示。
  • 大小參數以行大小的字節數表示,並以列和深度維度的元素數量表示。

暫無
暫無

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

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