91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何有效地從內(nèi)核中訪問設(shè)備的全局內(nèi)存

星星科技指導(dǎo)員 ? 來源:NVIDIA ? 作者:Mark Harris ? 2022-04-11 10:07 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

在前面的兩文章中,我們研究了如何在主機(jī)和設(shè)備之間高效地移動數(shù)據(jù)。在我們的 CUDA C / C ++系列的第六篇文章中,我們將討論如何有效地從內(nèi)核中訪問設(shè)備存儲器,特別是全局內(nèi)存。

在 CUDA 設(shè)備上有幾種內(nèi)存,每種內(nèi)存的作用域、生存期和緩存行為都不同。到目前為止,在本系列中,我們已經(jīng)使用了駐留在設(shè)備 DRAM 中的全局內(nèi)存,用于主機(jī)和設(shè)備之間的傳輸,以及內(nèi)核的數(shù)據(jù)輸入和輸出。這里的名稱global是指作用域,因?yàn)樗梢詮闹鳈C(jī)和設(shè)備訪問和修改。全局內(nèi)存可以像下面代碼片段的第一行那樣使用__device__de Clara 說明符在全局(變量)范圍內(nèi)聲明,或者使用cudaMalloc()動態(tài)分配并分配給一個常規(guī)的 C 指針變量,如第 7 行所示。全局內(nèi)存分配可以在應(yīng)用程序的生命周期內(nèi)保持。根據(jù)設(shè)備的計算能力,全局內(nèi)存可能被緩存在芯片上,也可能不在芯片上緩存。

__device__ int globalArray[256];

void foo()
{
    ...
    int *myDeviceMemory = 0;
    cudaError_t result = cudaMalloc(&myDeviceMemory, 256 * sizeof(int));
    ...

}在討論全局內(nèi)存訪問性能之前,我們需要改進(jìn)對 CUDA 執(zhí)行模型的理解。我們已經(jīng)討論了如何將線程被分組為線程塊分配給設(shè)備上的多處理器。在執(zhí)行過程中,有一個更精細(xì)的線程分組到warps。 GPU 上的多處理器以 SIMD (單指令多數(shù)據(jù))方式為每個扭曲執(zhí)行指令。所有當(dāng)前支持 CUDA – 的 GPUs 的翹曲尺寸(實(shí)際上是 SIMD 寬度)是 32 個線程。

全局內(nèi)存合并

將線程分組為扭曲不僅與計算有關(guān),而且與全局內(nèi)存訪問有關(guān)。設(shè)備coalesces全局內(nèi)存加載并存儲由一個 warp 線程發(fā)出的盡可能少的事務(wù),以最小化 DRAM 帶寬(在計算能力小于 2 . 0 的舊硬件上,事務(wù)合并在 16 個線程的一半扭曲內(nèi),而不是整個扭曲中)。為了弄清楚 CUDA 設(shè)備架構(gòu)中發(fā)生聚結(jié)的條件,我們在三個 Tesla 卡上進(jìn)行了一些簡單的實(shí)驗(yàn): a Tesla C870 (計算能力 1 . 0 )、 Tesla C1060 (計算能力 1 . 3 )和 Tesla C2050 (計算能力 2 . 0 )。

我們運(yùn)行兩個實(shí)驗(yàn),使用如下代碼(GitHub 上也有)中所示的增量內(nèi)核的變體,一個具有數(shù)組偏移量,這可能導(dǎo)致對輸入數(shù)組的未對齊訪問,另一個是對輸入數(shù)組的跨步訪問。

#include
#include

// 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: %sn", cudaGetErrorString(result));
    assert(result == cudaSuccess);
  }
#endif
  return result;
}

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

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

template
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<<>>(d_a, 0); // warm up

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

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

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

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

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

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

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

  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: %sn", prop.name);
  printf("Transfer size (MB): %dn", nMB);

  printf("%s Precisionn", bFp64 ? "Double" : "Single");

  if (bFp64) runTest(deviceId, nMB);
  else       runTest(deviceId, nMB);?

}此代碼可以通過傳遞“ fp64 ”命令行選項以單精度(默認(rèn)值)或雙精度運(yùn)行偏移量內(nèi)核和跨步內(nèi)核。每個內(nèi)核接受兩個參數(shù),一個輸入數(shù)組和一個表示訪問數(shù)組元素的偏移量或步長的整數(shù)。內(nèi)核在一系列偏移和跨距的循環(huán)中被稱為。

未對齊的數(shù)據(jù)訪問

下圖顯示了 Tesla C870 、 C1060 和 C2050 上的偏移內(nèi)核的結(jié)果。

設(shè)備內(nèi)存中分配的數(shù)組由 CUDA 驅(qū)動程序與 256 字節(jié)內(nèi)存段對齊。該設(shè)備可以通過 32 字節(jié)、 64 字節(jié)或 128 字節(jié)的事務(wù)來訪問全局內(nèi)存。對于 C870 或計算能力為 1 . 0 的任何其他設(shè)備,半線程的任何未對齊訪問(或半扭曲線程不按順序訪問內(nèi)存的對齊訪問)將導(dǎo)致 16 個獨(dú)立的 32 字節(jié)事務(wù)。由于每個 32 字節(jié)事務(wù)只請求 4 個字節(jié),因此可以預(yù)期有效帶寬將減少 8 倍,這與上圖(棕色線)中看到的偏移量(不是 16 個元素的倍數(shù))大致相同,對應(yīng)于線程的一半扭曲。

對于計算能力為 1 . 2 或 1 . 3 的 Tesla C1060 或其他設(shè)備,未對準(zhǔn)訪問的問題較少?;旧希ㄟ^半個線程對連續(xù)數(shù)據(jù)的未對齊訪問在幾個“覆蓋”請求的數(shù)據(jù)的事務(wù)中提供服務(wù)。由于未請求的數(shù)據(jù)正在傳輸,以及不同的半翹曲所請求的數(shù)據(jù)有些重疊,因此相對于對齊的情況仍然存在性能損失,但是這種損失遠(yuǎn)遠(yuǎn)小于 C870 。

計算能力為 2 . 0 的設(shè)備,如 Tesla C250 ,在每個多處理器中都有一個 L1 緩存,其行大小為 128 字節(jié)。該設(shè)備將線程的訪問合并到盡可能少的緩存線中,從而導(dǎo)致對齊對跨線程順序內(nèi)存訪問吞吐量的影響可以忽略不計。

快速內(nèi)存訪問

步幅內(nèi)核的結(jié)果如下圖所示。

對于快速的全局內(nèi)存訪問,我們有不同的看法。對于大步進(jìn),無論架構(gòu)版本如何,有效帶寬都很差。這并不奇怪:當(dāng)并發(fā)線程同時訪問物理內(nèi)存中相距很遠(yuǎn)的內(nèi)存地址時,硬件就沒有機(jī)會合并這些訪問。從上圖中可以看出,在 Tesla C870 上,除 1 以外的任何步幅都會導(dǎo)致有效帶寬大幅降低。這是因?yàn)?compute capability 1 . 0 和 1 . 1 硬件需要跨線程進(jìn)行線性、對齊的訪問以進(jìn)行合并,因此我們在 offset 內(nèi)核中看到了熟悉的 1 / 8 帶寬。 Compute capability 1 . 2 及更高版本的硬件可以將訪問合并為對齊的段( CC 1 . 2 / 1 . 3 上為 32 、 64 或 128 字節(jié)段,在 CC 2 . 0 及更高版本上為 128 字節(jié)緩存線),因此該硬件可以產(chǎn)生平滑的帶寬曲線。

當(dāng)訪問多維數(shù)組時,線程通常需要索引數(shù)組的更高維,因此快速訪問是不可避免的。我們可以使用一種名為共享內(nèi)存的 CUDA 內(nèi)存來處理這些情況。共享內(nèi)存是一個線程塊中所有線程共享的片上內(nèi)存。共享內(nèi)存的一個用途是將多維數(shù)組的 2D 塊以合并的方式從全局內(nèi)存提取到共享內(nèi)存中,然后讓連續(xù)的線程跨過共享內(nèi)存塊。與全局內(nèi)存不同,對共享內(nèi)存的快速訪問沒有懲罰。我們將在下一篇文章中詳細(xì)介紹共享內(nèi)存。

概括

在這篇文章中,我們討論了如何從 CUDA 內(nèi)核代碼中有效地訪問全局內(nèi)存的一些方面。設(shè)備上的全局內(nèi)存訪問與主機(jī)上的數(shù)據(jù)訪問具有相同的性能特征,即數(shù)據(jù)局部性非常重要。在早期的 CUDA 硬件中,內(nèi)存訪問對齊和跨線程的局部性一樣重要,但在最近的硬件上,對齊并不是什么大問題。另一方面,快速的內(nèi)存訪問會損害性能,使用片上共享內(nèi)存可以減輕這種影響。在下一篇文章中,我們將詳細(xì)探討共享內(nèi)存,之后的文章中,我們將展示如何使用共享內(nèi)存來避免在矩陣轉(zhuǎn)置過程中出現(xiàn)跨步全局內(nèi)存訪問。

關(guān)于作者

Mark Harris 是 NVIDIA 杰出的工程師,致力于 RAPIDS 。 Mark 擁有超過 20 年的 GPUs 軟件開發(fā)經(jīng)驗(yàn),從圖形和游戲到基于物理的模擬,到并行算法和高性能計算。當(dāng)他還是北卡羅來納大學(xué)的博士生時,他意識到了一種新生的趨勢,并為此創(chuàng)造了一個名字: GPGPU (圖形處理單元上的通用計算)。

審核編輯:郭婷

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 存儲器
    +關(guān)注

    關(guān)注

    39

    文章

    7738

    瀏覽量

    171654
  • NVIDIA
    +關(guān)注

    關(guān)注

    14

    文章

    5592

    瀏覽量

    109721
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點(diǎn)推薦

    Linux內(nèi)核伙伴系統(tǒng)內(nèi)存申請函數(shù)詳解:原理到實(shí)戰(zhàn)

    在 Linux 內(nèi)核,內(nèi)存管理是整個系統(tǒng)穩(wěn)定運(yùn)行的基石,而伙伴系統(tǒng)(Buddy System) 作為內(nèi)核物理內(nèi)存分配的核心機(jī)制,更是驅(qū)動開
    的頭像 發(fā)表于 02-10 16:58 ?3630次閱讀
    Linux<b class='flag-5'>內(nèi)核</b>伙伴系統(tǒng)<b class='flag-5'>內(nèi)存</b>申請函數(shù)詳解:<b class='flag-5'>從</b>原理到實(shí)戰(zhàn)

    initrd 沒有帶有自編譯內(nèi)核的 nvme 設(shè)備?

    你好!我使用 nvme 的 -starfive 內(nèi)核在 visionfive2 上運(yùn)行 debian。當(dāng)我根據(jù)內(nèi)核升級我總是在缺少 root 設(shè)備的情況下進(jìn)入 initrd。 因此,
    發(fā)表于 02-10 06:40

    Linux 6.8 內(nèi)核 - 錯誤:找不到 cmdline 扁平化設(shè)備樹怎么解決?

    找不到有效設(shè)備樹 啟動舊的 5.15 內(nèi)核顯示如下: 解壓縮內(nèi)核映像 0x44000000到0x40200000的移動圖像,end=4
    發(fā)表于 02-09 07:13

    keilc語言的動態(tài)分配內(nèi)存

    在C程序,通常將內(nèi)存劃分為以下六個區(qū)域: (1)內(nèi)核區(qū)域。這塊區(qū)域是操作系統(tǒng)的,用戶不能使用。 (2)棧區(qū)。主要用于存放運(yùn)行函數(shù)而分配的局部變量、函數(shù)參數(shù)、返回數(shù)據(jù)、返回地址等。棧內(nèi)存
    發(fā)表于 01-21 06:04

    【「Linux 設(shè)備驅(qū)動開發(fā)(第 2 版)」閱讀體驗(yàn)】+讀深入理解Linux內(nèi)核內(nèi)存分配

    每個內(nèi)存地址是虛擬的,不是直接指向RAM的任何地址。當(dāng)用戶訪問內(nèi)存的存儲單元時,都會進(jìn)行地址轉(zhuǎn)換以匹配相應(yīng)的物理
    發(fā)表于 01-16 20:05

    【「Linux 設(shè)備驅(qū)動開發(fā)(第 2 版)」閱讀體驗(yàn)】Linux內(nèi)核開發(fā)基礎(chǔ)

    CPU上的調(diào)度程序來工作,自旋鎖保護(hù)的資源,在同一時間只能由一個CPU使用/訪問,這使得自旋鎖適用于保證對稱多處理的安全性,也適用于執(zhí)行原子任務(wù) 在內(nèi)核源碼,使用DEFINE_SPINLOCK宏靜態(tài)
    發(fā)表于 01-12 22:45

    C語言訪問某特定內(nèi)存位置

    嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問某特定的內(nèi)存位置的特點(diǎn)。在某工程,要求設(shè)置一絕對地址為0x67a9的整型變量的值為0xaa66。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務(wù)。 考察點(diǎn)
    發(fā)表于 12-22 15:42

    Linux內(nèi)核模塊的加載機(jī)制

    符號表(/proc/kallsyms)或已加載模塊查找匹配。2、重定位修正:修改代碼的地址引用(如函數(shù)調(diào)用、全局變量訪問)為實(shí)際加載地址。處理版本校驗(yàn)(__versions段),確
    發(fā)表于 11-25 06:59

    如何為蜂鳥添加DDR內(nèi)存擴(kuò)展

    ,蜂鳥本身提供了外部存儲器的訪問接口,在e203_subsys_mems.v文件可以找到: 同時在這個源文件實(shí)現(xiàn)了一主多的總線系統(tǒng),可以看到sysmem被分配到0x
    發(fā)表于 10-31 06:07

    通過sysmem接口擴(kuò)展內(nèi)存空間

    存儲器的訪問接口,在e203_subsys_mems.v文件可以找到: 同時在這個源文件實(shí)現(xiàn)了一主多的總線系統(tǒng),可以看到sysmem被分配到0x80000000到0xFFFFF
    發(fā)表于 10-24 08:12

    請問STM32MP257CM33內(nèi)核能否訪問以太網(wǎng)口Ethernet?

    STM32MP257CM33內(nèi)核能否訪問以太網(wǎng)口Ethernet? 能否有現(xiàn)成的例程Samples借鑒?
    發(fā)表于 05-14 08:23

    請問STM32MP257CM33內(nèi)核能否訪問以太網(wǎng)口Ethernet?

    STM32MP257CM33內(nèi)核能否訪問以太網(wǎng)口Ethernet? 能否有現(xiàn)成的例程Samples借鑒?
    發(fā)表于 04-27 07:06

    請問STM32MP257CM33內(nèi)核能否訪問以太網(wǎng)口Ethernet?

    STM32MP257CM33內(nèi)核能否訪問以太網(wǎng)口Ethernet? 能否有現(xiàn)成的例程Samples借鑒?
    發(fā)表于 04-25 06:12

    如何在Linux內(nèi)核5.18版本之后和64位架構(gòu)內(nèi)核空間調(diào)用ioctl?

    我嘗試在最近的內(nèi)核重新構(gòu)建以前版本 (4.19) 的 Linux 設(shè)備驅(qū)動程序,即嵌入式平臺上的 6.1.22,ARM64 架構(gòu)。 驅(qū)動程序管理 tty 設(shè)備。 當(dāng)我調(diào)用類似于用戶空
    發(fā)表于 04-02 06:06

    飛凌嵌入式ElfBoard ELF 1板卡-內(nèi)核空間與用戶空間的數(shù)據(jù)拷貝之?dāng)?shù)據(jù)拷貝介紹

    在Linux系統(tǒng),內(nèi)核空間和用戶空間是兩個獨(dú)立的地址空間,它們有不同的訪問權(quán)限和內(nèi)存保護(hù)機(jī)制。在內(nèi)核空間和用戶空間之間進(jìn)行數(shù)據(jù)傳輸時,需要
    發(fā)表于 03-19 08:55