從案例學習cuda編程——線程模型和顯存模型

1. cuda介紹

????????CUDA(Compute Unified Device Architecture,統一計算設備架構)是NVIDIA推出的一種并行計算平臺和編程模型。它允許開發者利用NVIDIA GPU的強大計算能力來加速計算密集型任務。CUDA通過提供一套專門的API和編程接口,使得開發者能夠編寫在GPU上運行的程序,從而實現大規模并行計算。

首先來看我們的 CUDA 程序代碼:

#include <stdio.h>
#include <iostream>
#include <cuda_runtime.h>
using namespace std;__global__ void hello_world(void) {printf("thread idx: %d\n", threadIdx.x);if (threadIdx.x == 0) {printf("GPU: hello_world\n");}
}int main(void) {printf("CPU: Hello world!\n");hello_world<<<1, 32>>>();cudaDeviceSynchronize();cudaError_t err = cudaGetLastError();if (err != cudaSuccess) {cerr << "CUDA error: " << cudaGetErrorString(err) << endl;} else { cout << "GPU: Hello world finished!" << endl; }cout << "CPU: Hello world finished!" << endl;return 0;
}

這個程序雖然簡單,卻包含了 CUDA 編程的多個核心要素,讓我們逐一解析。?

1. CUDA 程序的特殊頭文件與命名空間?

????????CUDA(Compute Unified Device Architecture,統一計算設備架構)是NVIDIA推出的一種GPU編程組件,它是一種原生支持GPU編程的軟硬件架構。CUDA突破了過去必須通過圖形API來使用GPU計算能力的限制,使得開發者可以直接在GPU上編寫和執行通用計算程序。以下是CUDA技術棧的分層結構及其主要組成部分:

硬件驅動層(CUDA Driver)

  • 功能:負責與GPU硬件通信,提供底層支持。

  • 作用:作為硬件與軟件之間的橋梁,確保CUDA程序能夠正確地與GPU硬件交互。

應用編程接口(API)與運行時(Runtime)

  • 功能:為開發者提供簡潔易用的編程接口和執行環境。

  • 作用:簡化開發流程,使得開發者可以更高效地編寫和運行CUDA程序。

????????CUDA工具鏈中的核心組件之一,專門用于編譯含有CUDA C/C++擴展語法的源代碼文件(通常以.cu為擴展名)。編譯時需要使用nvcc hello_world.cu -o hello_world進行編譯

  • #include <cuda_runtime.h>:這是 CUDA 運行時 API 的頭文件,包含了 CUDA 編程所需的各種函數聲明和數據類型定義,是 CUDA 程序必不可少的頭文件。?
  • 標準 C++ 頭文件:stdio.h和iostream用于 CPU 端的輸入輸出操作,CUDA 程序可以無縫結合 C++ 標準庫使用。?

2. 核函數(Kernel Function)?

核函數是 CUDA 編程中最核心的概念,是在 GPU 上執行的函數,代碼中:?

__global__ void hello_world(void)?

  • __global__:這是 CUDA 特有的函數修飾符,表明該函數是核函數。核函數有以下特點:?
  • 在 CPU 上調用,在 GPU 上執行?
  • 返回類型必須為 void?
  • 可以通過特殊的執行配置符指定啟動的線程數量?

3. 核函數的調用方式?

核函數的調用方式與普通函數不同,需要使用特殊的執行配置符:?

hello_world<<<1, 32>>>();?

<<<...>>>:這是 CUDA 特有的執行配置符,用于指定在 GPU 上啟動的線程數量和組織方式?

    • 第一個參數1:表示啟動 1 個線程塊(block)?
    • 第二個參數32:表示每個線程塊包含 32 個線程(thread)?
    • 因此,這個配置總共在 GPU 上啟動了 1×32=32 個線程并行執行?

    4. 線程的標識與索引?

    在核函數中,我們可以通過threadIdx變量獲取當前線程在其所屬線程塊中的索引:?

    printf("thread idx: %d\n", threadIdx.x);?

    • threadIdx:是一個uint3類型的結構體,包含三個成員:x、y、z,分別表示線程在三維線程塊中的三個維度上的索引?
    • 為什么設計成三維?這是因為許多實際問題(如 3D 圖形渲染、三維物理模擬等)具有天然的三維特性,三維索引可以更自然地映射這些問題?
    • 在這個例子中,我們只使用了一維索引threadIdx.x,表示線程在 x 維度上的索引(范圍 0-31)?

    5. CPU 與 GPU 的同步機制?

    由于 CPU 和 GPU 是獨立的處理器,它們的執行是異步的。為了確保 CPU 等待 GPU 完成計算后再繼續執行,需要使用同步函數:?

    cudaDeviceSynchronize();?

    • 這個函數會阻塞 CPU 的執行,直到 GPU 完成所有之前提交的計算任務?
    • 同步操作對于確保數據一致性和正確的執行順序非常重要?

    6. CUDA 錯誤處理機制?

    CUDA 操作可能會出現各種錯誤,良好的錯誤處理是編寫健壯 CUDA 程序的關鍵:?

    cudaError_t err = cudaGetLastError();?if (err != cudaSuccess) {?cerr << "CUDA error: " << cudaGetErrorString(err) << endl;?}?
    • cudaGetLastError():獲取最近一次 CUDA 操作產生的錯誤代碼?
    • cudaError_t:CUDA 錯誤類型,cudaSuccess表示操作成功?
    • cudaGetErrorString():將錯誤代碼轉換為人類可讀的錯誤信息?

    程序執行流程與結果分析?

    當我們編譯并運行這個程序時,會得到類似以下的輸出:?

    CPU: Hello world!
    thread idx: 0
    thread idx: 1
    thread idx: 2
    thread idx: 3
    thread idx: 4
    thread idx: 5
    thread idx: 6
    thread idx: 7
    thread idx: 8
    thread idx: 9
    thread idx: 10
    thread idx: 11
    thread idx: 12
    thread idx: 13
    thread idx: 14
    thread idx: 15
    thread idx: 16
    thread idx: 17
    thread idx: 18
    thread idx: 19
    thread idx: 20
    thread idx: 21
    thread idx: 22
    thread idx: 23
    thread idx: 24
    thread idx: 25
    thread idx: 26
    thread idx: 27
    thread idx: 28
    thread idx: 29
    thread idx: 30
    thread idx: 31
    GPU: hello_world
    GPU: Hello world finished!
    CPU: Hello world finished!
    • 首先 CPU 輸出 "CPU: Hello world!"?
    • 然后啟動 32 個 GPU 線程并行執行核函數?
    • 每個線程輸出自己的索引值,其中索引為 0 的線程還會額外輸出 "GPU: hello_world"?
    • 所有 GPU 線程執行完成后,CPU 輸出執行完成的信息?

    需要注意的是,GPU 線程的執行順序是不確定的,因此線程索引的輸出順序可能每次運行都不同,這體現了并行計算的特性。?

    2.?CUDA線程分層結構與執行模型

    ?????????在CUDA的執行模型中,線程是CUDA中最小的執行單元。多個線程可以組織成一個線程塊(Thread Block),而多個線程塊又組成一個網格(Grid)。

    ????????線程塊中的線程數量是有限制的,因為同一個線程塊內的所有線程必須運行在同一個流多處理器(Streaming Multiprocessor, SM)上,并共享該SM的有限資源(如寄存器、共享內存等)。

    ????????線程塊中的線程會以32個線程為一組進行調度(調度的時候受限于一個SM中的可用資源),這組線程被稱為線程束(Warp)。

    ????????線程束(Warp)是SM中最基本的執行單元。當一個線程塊被啟動后,其中的所有線程會被劃分成多個線程束,每個線程束包含32個線程。同一warp中的線程會同時執行相同的指令,但各自處理不同的數據,從而實現數據并行計算。

    ????????從執行模型中軟硬件的對應關系來看,我們可以清晰地看到前面介紹的三級線程分層結構:線程(Thread)→線程塊(Block)→網格(Grid)。

    ?示例:

    #include <cstdio>
    #define BLOCK_SIZE 256__global__ void vecAdd(int *A, int *B, int *C, int n) {int i = blockIdx.x * blockDim.x + threadIdx.x;if (i < n) {C[i] = A[i] + B[i];}
    }int main() {int N = 10000;size_t size = N * sizeof(int);// 主機內存分配與初始化int *A = (int*)malloc(size);int *B = (int*)malloc(size);int *C = (int*)malloc(size);for (int i = 0; i < N; i++) {A[i] = i;B[i] = i * i;  // 修正冪運算錯誤}// 設備內存分配int *d_A, *d_B, *d_C;cudaMalloc((void**)&d_A, size);cudaMalloc((void**)&d_B, size);cudaMalloc((void**)&d_C, size);// 主機到設備的數據拷貝cudaMemcpy(d_A, A, size, cudaMemcpyHostToDevice);cudaMemcpy(d_B, B, size, cudaMemcpyHostToDevice);// 計算線程塊數量并啟動核函數int Num_Blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;vecAdd<<<Num_Blocks, BLOCK_SIZE>>>(d_A, d_B, d_C, N);cudaDeviceSynchronize();  // 等待GPU完成計算// 設備到主機的數據拷貝(修正方向錯誤)cudaMemcpy(C, d_C, size, cudaMemcpyDeviceToHost);// 結果驗證for (int i = 0; i < N; i++) {if (C[i] != A[i] + B[i]) {printf("Error index: %d, Expected: %d, Got: %d.\n", i, A[i]+B[i], C[i]);}}printf("Vector addition completed successfully.\n");// 資源釋放free(A);free(B);free(C);cudaFree(d_A);cudaFree(d_B);cudaFree(d_C);return 0;
    }

    程序功能與整體架構?

    這段代碼實現了兩個向量的逐元素加法(C [i] = A [i] + B [i]),其中:?

    • 向量長度 N=10000?
    • 使用 CPU 初始化輸入數據(A [i]=i,B [i]=i2)?
    • 通過 GPU 并行計算實現向量加法?
    • 最后在 CPU 端驗證計算結果?

    程序采用了 CUDA 編程的標準流程:主機初始化→數據傳輸→GPU 計算→結果回傳→驗證釋放,這一流程是所有 CUDA 應用的基礎框架。?

    核心技術點詳解?

    1. CUDA 內存模型與數據傳輸?

    CUDA 采用異構內存模型,CPU(主機)和 GPU(設備)擁有各自獨立的內存空間,程序中清晰體現了這一特性:

    // 主機內存分配(CPU可訪問)
    int *A = (int*)malloc(size);
    int *B = (int*)malloc(size);
    int *C = (int*)malloc(size);// 設備內存分配(僅GPU可訪問)
    int *d_A, *d_B, *d_C;
    cudaMalloc((void**)&d_A, size);
    cudaMalloc((void**)&d_B, size);
    cudaMalloc((void**)&d_C, size);
    • malloc:分配主機內存(Host Memory)?
    • cudaMalloc:分配設備內存(Device Memory),注意其特殊的指針地址傳遞方式(void**)&d_A

    數據傳輸通過cudaMemcpy實現,需要明確指定傳輸方向:?

    // 主機到設備(Host → Device)
    cudaMemcpy(d_A, A, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, B, size, cudaMemcpyHostToDevice);// 設備到主機(Device → Host)
    cudaMemcpy(C, d_C, size, cudaMemcpyDeviceToHost);

    cudaMemcpy的參數遵循目標地址→源地址的順序,這是容易出錯的關鍵點,必須與傳輸方向參數匹配。?

    2. 線程組織與索引計算?

    CUDA 通過網格(Grid)- 線程塊(Block)- 線程(Thread) 的三級結構組織并行線程,這段代碼展示了一維線程組織的典型方式:

    // 定義每個線程塊的線程數量#define BLOCK_SIZE 256// 計算所需線程塊數量int Num_Blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;// 啟動核函數時指定線程組織方式vecAdd<<<Num_Blocks, BLOCK_SIZE>>>(d_A, d_B, d_C, N);

    核函數內部通過以下公式計算全局線程索引:?

    int i = blockIdx.x * blockDim.x + threadIdx.x;
    • blockIdx.x:線程塊在網格中的索引(0 到 Num_Blocks-1)?
    • blockDim.x:每個線程塊的線程數(即 BLOCK_SIZE)?
    • threadIdx.x:線程在所屬線程塊中的索引(0 到 BLOCK_SIZE-1)?

    這種索引計算方式是 CUDA 一維問題的標準模式,通過組合塊索引和線程索引,為每個線程分配唯一的全局索引。

    3.顯存分層結構和管理(存儲模型)

    在 CUDA 編程中,寄存器、共享顯存和全局顯存是我們最常關注的三種顯存層級。它們構成了 GPU 內存層次結構的核心,在存儲容量訪問速度方面呈現出典型的層級特性:

    1. 從容量來看:寄存器的容量最小,共享顯存次之,全局顯存擁有最大的存儲空間。

    2. 從訪問速度來看:寄存器訪問速度最快,共享顯存次之,全局顯存相對較慢。

    此外,這三種內存的可見范圍(作用域)也逐級擴大

    • 寄存器是線程私有的內存空間,只能被當前線程訪問,通常用于存儲核函數中的臨時變量;

    • 共享顯存由同一個線程塊內的所有線程共享,是實現線程間協作的關鍵機制;

    • 全局顯存的作用范圍覆蓋整個設備,平時我們所說的顯卡顯存容量(如多少GB),指的就是全局顯存,它可以被所有線程訪問。

    ?

    4.利用共享顯存優化Cuda核函數

    • 每個流式多處理器(SM)都包含一小塊低延遲的內存池,稱為共享顯存(Shared Memory)。這塊內存被當前在該 SM 上執行的線程塊(block)中的所有線程所共享,是實現線程間高效通信與協作的關鍵資源。

    • 與全局顯存(Global Memory)相比,共享顯存具有顯著的優勢(片上和片外的區別):其訪問延遲大約低 20~30 倍,帶寬則高出約 10 倍。因此,在并行計算中合理利用共享顯存,可以大幅提高程序的性能和效率。

    示例:

    歸約(Reduction)是并行計算中最基礎也最核心的操作之一,它通過將大規模數據逐步聚合為單一結果(如求和、求最值),廣泛應用于科學計算、數據分析等領域。本文將通過一段對比全局內存與共享內存歸約的 CUDA 代碼,深入解析 GPU 并行歸約的實現原理、核心技術點及性能優化邏輯。

    歸約的本質是 “多對一” 的聚合操作。例如對數組[a0, a1, a2, ..., an-1]求和,串行邏輯是sum = a0 + a1 + ... + an-1,但在 GPU 上,我們可以讓多個線程同時工作:每個線程處理部分元素,通過多輪合并最終得到總和。

    這段代碼實現了兩種歸約方案:

    • 基于全局內存的歸約(reduceGmem:直接操作 GPU 全局內存進行數據合并;
    • 基于共享內存的歸約(reduceSmem:先將數據加載到 GPU 共享內存(更快的片上內存),再進行合并。

    通過對比兩者的性能,我們能直觀理解 GPU 內存層次對程序效率的影響。

    #include <iostream>
    #include <cstdlib>  // 用于動態內存分配
    #define BLOCK_SIZE 1024  // 線程塊大小,需與歸約邏輯匹配// 方法1:基于全局內存的歸約
    __global__ void reduceGmem(int *g_idata, int *g_odata, int n) {// 線程塊內索引(關鍵修正:使用塊內索引而非全局索引)unsigned int tid = threadIdx.x;// 定位當前線程塊處理的數據段起始地址int *idata = g_idata + blockIdx.x * blockDim.x;// 邊界檢查:若當前線程處理的全局索引超出數據范圍,直接返回if (blockIdx.x * blockDim.x + tid >= n)return;// 歸約階段1:1024→512(僅當塊大小≥1024時執行)if (blockDim.x >= 1024 && tid < 512) {idata[tid] += idata[tid + 512];}__syncthreads();  // 塊內同步,確保數據更新完成// 歸約階段2:512→256if (blockDim.x >= 512 && tid < 256) {idata[tid] += idata[tid + 256];}__syncthreads();// 歸約階段3:256→128if (blockDim.x >= 256 && tid < 128) {idata[tid] += idata[tid + 128];}__syncthreads();// 歸約階段4:128→64if (blockDim.x >= 128 && tid < 64) {idata[tid] += idata[tid + 64];}__syncthreads();// 最終階段:64→1(利用warp內隱式同步)if (tid < 32) {volatile int *vmem = idata;  // 修正:添加int類型vmem[tid] += vmem[tid + 32];vmem[tid] += vmem[tid + 16];vmem[tid] += vmem[tid + 8];vmem[tid] += vmem[tid + 4];vmem[tid] += vmem[tid + 2];vmem[tid] += vmem[tid + 1];}// 每個線程塊的0號線程保存歸約結果if (tid == 0) {g_odata[blockIdx.x] = idata[0];}
    }// 方法2:基于共享內存的歸約(補充完整邏輯)
    __global__ void reduceSmem(int *g_idata, int *g_odata, int n) {__shared__ int smem[BLOCK_SIZE];  // 共享內存大小與線程塊大小一致(關鍵修正)unsigned int tid = threadIdx.x;unsigned int global_tid = blockIdx.x * blockDim.x + tid;// 1. 加載數據到共享內存(帶邊界檢查)smem[tid] = (global_tid < n) ? g_idata[global_tid] : 0;__syncthreads();// 2. 共享內存內歸約(與全局內存歸約邏輯類似,但訪問更快)if (blockDim.x >= 1024 && tid < 512) {smem[tid] += smem[tid + 512];}__syncthreads();if (blockDim.x >= 512 && tid < 256) {smem[tid] += smem[tid + 256];}__syncthreads();if (blockDim.x >= 256 && tid < 128) {smem[tid] += smem[tid + 128];}__syncthreads();if (blockDim.x >= 128 && tid < 64) {smem[tid] += smem[tid + 64];}__syncthreads();// 最終階段歸約if (tid < 32) {volatile int *vmem = smem;vmem[tid] += vmem[tid + 32];vmem[tid] += vmem[tid + 16];vmem[tid] += vmem[tid + 8];vmem[tid] += vmem[tid + 4];vmem[tid] += vmem[tid + 2];vmem[tid] += vmem[tid + 1];}// 3. 保存每個線程塊的歸約結果if (tid == 0) {g_odata[blockIdx.x] = smem[0];}
    }int main() {const int N = 102400;  // 數據規模int *h_data = new int[N];  // 主機內存(動態分配,避免棧溢出)// 初始化主機數據(0到N-1的累加和,用于后續驗證)for (int i = 0; i < N; i++) {h_data[i] = i;}// 設備內存分配int *d_idata, *d_odata1, *d_odata2;cudaMalloc((void**)&d_idata, sizeof(int) * N);cudaMalloc((void**)&d_odata1, sizeof(int) * ((N + BLOCK_SIZE - 1) / BLOCK_SIZE));  // 存儲方法1的部分和cudaMalloc((void**)&d_odata2, sizeof(int) * ((N + BLOCK_SIZE - 1) / BLOCK_SIZE));  // 存儲方法2的部分和// 主機→設備數據拷貝cudaMemcpy(d_idata, h_data, sizeof(int) * N, cudaMemcpyHostToDevice);// 計算線程塊數量int num_blocks = (N + BLOCK_SIZE - 1) / BLOCK_SIZE;// 計時變量與事件cudaEvent_t start1, stop1, start2, stop2;cudaEventCreate(&start1);cudaEventCreate(&stop1);cudaEventCreate(&start2);cudaEventCreate(&stop2);// 測試基于全局內存的歸約cudaEventRecord(start1);reduceGmem<<<num_blocks, BLOCK_SIZE>>>(d_idata, d_odata1, N);  // 修正:使用<<<>>>cudaEventRecord(stop1);cudaEventSynchronize(stop1);  // 等待計算完成// 測試基于共享內存的歸約cudaEventRecord(start2);reduceSmem<<<num_blocks, BLOCK_SIZE>>>(d_idata, d_odata2, N);  // 修正:核函數參數與名稱匹配cudaEventRecord(stop2);cudaEventSynchronize(stop2);// 計算耗時float time1, time2;cudaEventElapsedTime(&time1, start1, stop1);cudaEventElapsedTime(&time2, start2, stop2);std::cout << "reduceGmem time: " << time1 << " ms" << std::endl;std::cout << "reduceSmem time: " << time2 << " ms" << std::endl;// 資源釋放(關鍵修正:避免內存泄漏)delete[] h_data;cudaFree(d_idata);cudaFree(d_odata1);cudaFree(d_odata2);cudaEventDestroy(start1);cudaEventDestroy(stop1);cudaEventDestroy(start2);cudaEventDestroy(stop2);return 0;
    }
    

    1. 基于全局內存的歸約(reduceGmem

    __global__ void reduceGmem(int *g_idata, int *g_odata, int n) {unsigned int tid = threadIdx.x;  // 塊內線程索引int *idata = g_idata + blockIdx.x * blockDim.x;  // 當前塊處理的數據段// 邊界檢查:避免訪問超出數組范圍的內存if (blockIdx.x * blockDim.x + tid >= n) return;// 多階段歸約:1024→512→256→...→1if (blockDim.x >= 1024 && tid < 512) idata[tid] += idata[tid + 512];__syncthreads();if (blockDim.x >= 512 && tid < 256) idata[tid] += idata[tid + 256];__syncthreads();if (blockDim.x >= 256 && tid < 128) idata[tid] += idata[tid + 128];__syncthreads();if (blockDim.x >= 128 && tid < 64) idata[tid] += idata[tid + 64];__syncthreads();// 最終階段:利用warp內隱式同步if (tid < 32) {volatile int *vmem = idata;  // 確保內存訪問不被編譯器優化vmem[tid] += vmem[tid + 32];vmem[tid] += vmem[tid + 16];vmem[tid] += vmem[tid + 8];vmem[tid] += vmem[tid + 4];vmem[tid] += vmem[tid + 2];vmem[tid] += vmem[tid + 1];}// 保存當前塊的歸約結果if (tid == 0) g_odata[blockIdx.x] = idata[0];
    }
    關鍵設計思路:
    • 數據劃分:輸入數組按線程塊大小(BLOCK_SIZE=1024)分成多個段,每個線程塊處理一段數據,最終輸出 “部分和”(每個塊的結果)。
    • 分階段合并:通過 4 輪 “二分歸約” 將 1024 個元素逐步縮減為 64 個,每輪只讓前半線程參與(如tid < 512處理tidtid+512的合并),確保無重復計算。
    • 線程同步:每輪合并后用__syncthreads()阻塞塊內所有線程,確保所有線程完成當前輪次后再進入下一輪(避免讀取未更新的數據)。
    • Warp 優化:最后 64→1 的合并僅用前 32 個線程(一個線程束,GPU 的基本執行單元),利用 warp 內線程的隱式同步(無需__syncthreads()),同時用volatile防止編譯器優化導致的內存訪問錯誤。

    注:

    • __syncthreads():線程塊內的同步屏障,所有線程必須到達此函數后才能繼續執行,用于避免 “先寫后讀” 的數據沖突(如線程 A 未寫完數據,線程 B 就讀取)。
    • volatile關鍵字:在 warp 內操作時,強制編譯器從內存而非寄存器讀取數據,確保線程能獲取到其他線程更新的最新值(warp 內線程無顯式同步,依賴硬件執行一致性)。

    2. 基于共享內存的歸約(reduceSmem

    __global__ void reduceSmem(int *g_idata, int *g_odata, int n) {__shared__ int smem[BLOCK_SIZE];  // 共享內存:塊內線程共享的片上內存unsigned int tid = threadIdx.x;unsigned int global_tid = blockIdx.x * blockDim.x + tid;// 步驟1:加載數據到共享內存(帶邊界檢查)smem[tid] = (global_tid < n) ? g_idata[global_tid] : 0;__syncthreads();// 步驟2:共享內存內歸約(邏輯與全局內存一致,但訪問更快)if (blockDim.x >= 1024 && tid < 512) smem[tid] += smem[tid + 512];__syncthreads();if (blockDim.x >= 512 && tid < 256) smem[tid] += smem[tid + 256];__syncthreads();// ... 后續階段與reduceGmem相同 ...// 步驟3:保存結果if (tid == 0) g_odata[blockIdx.x] = smem[0];
    }
    與全局內存歸約的核心差異:
    • 引入共享內存:先將全局內存數據拷貝到smem(共享內存),后續所有合并操作都在smem中進行。
    • 性能優勢:共享內存是 GPU 芯片上的高速緩存(訪問延遲約為全局內存的 1/100),且支持線程塊內的低延遲數據共享,大幅減少全局內存訪問次數(全局內存是 GPU 性能瓶頸之一)。

    運行程序后,通常會觀察到reduceSmem的耗時顯著低于reduceGmem,例如:

    reduceGmem time: 30.91 ms
    reduceSmem time: 0.018432 ms

    性能差異的核心原因:

    • 全局內存的 “高延遲 + 低帶寬利用率”reduceGmem每輪合并都需要訪問全局內存,而全局內存的帶寬利用率受訪問模式限制(即使合并訪問,效率仍低于共享內存)。
    • 共享內存的 “低延遲 + 高復用”reduceSmem僅在加載階段訪問 1 次全局內存,后續合并全在共享內存中進行,且共享內存支持線程塊內的高效數據復用。

    ?????????

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/bicheng/92259.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/bicheng/92259.shtml
    英文地址,請注明出處:http://en.pswp.cn/bicheng/92259.shtml

    如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

    相關文章

    進階向:YOLOv11模型輕量化

    YOLOv11模型輕量化詳解:從理論到實踐 引言 YOLO(You Only Look Once)系列模型因其高效的實時檢測能力而廣受歡迎。YOLOv11作為該系列的最新演進版本,在精度和速度上均有顯著提升。然而,原始模型對計算資源的需求較高,難以在邊緣設備或移動端部署。輕量化技術通過減少模…

    2025-08 安卓開發面試拷打記錄(面試題)

    想跑路了&#xff0c;開始學八股&#xff0c;幾個主動找的大廠試了下水&#xff0c;后續看情況更新。樓主一年經驗&#xff0c;學的c被騙來干安卓&#xff0c;雙非本科。2025-07-31 小鵬匯天 安卓開發一面synchronizedhandler視圖刷新binderjvm垃圾回收內存泄漏排查glide緩…

    風丘助力混合動力汽車工況測試:精準采集整車信號解決方案

    一、背景 混合動力汽車是介于純電動汽車與燃油汽車兩者之間的一種新能源汽車。它既包含純電動汽車無污染、啟動快的優勢&#xff0c;又擁有燃油車續航便捷、不受電池容量限制的特點。在當前環境下&#xff0c;混合動力汽車比純電動汽車更符合目前的市場需求。 然而&#xff…

    ??MCU程序的存儲方式與存儲區域大小要求?

    程序的段的存儲方式與存儲區域大小要求 程序的存儲和運行涉及 ROM&#xff08;Flash/非易失性存儲器&#xff09; 和 RAM&#xff08;易失性存儲器&#xff09; 的分配&#xff0c;不同段在存儲和運行時具有不同的特性。以下是詳細的分類和計算方式&#xff1a;1. 程序文件的存…

    Lesson 31 Success story

    Lesson 31 Success story 詞匯 retire v.退休,退役[運動]去睡覺 構成:re-表示重復 tire v.感到累一tried a.累的 tyre n.輪胎 用法:retire from 單位 從…退休(過去時) 例句:他從學校退休了。 He retired from our school. retire例句: 1.他越來越老了&#xff0c;他即將退休。…

    2025年8月4日私魚創作平臺v1.0.4公測版更新發布-完成大部分功能包含關注創作者以及發布作品及合集功能優雅草科技

    2025年8月4日私魚創作平臺v1.0.4公測版更新發布-完成大部分功能包含關注創作者以及發布作品及合集功能優雅草科技 鯨魚小說分銷系統介紹 優雅草私魚創作系統——產品介紹 系統概述 優雅草私魚創作系統&#xff08;簡稱“私魚”&#xff09;是一款專注于私域流量運營的垂直化…

    鷓鴣云:光伏電站的“智慧中樞”,精準調控逆變器

    光伏電站如星辰散落于大地&#xff0c;那些默默工作的逆變器便是每一處光芒的關鍵心臟。然而&#xff0c;分布廣袤、設備眾多&#xff0c;傳統運維如盲人摸象&#xff0c;效率低下&#xff0c;故障難尋&#xff0c;白白流失寶貴電能。鷓鴣云光伏運維軟件應時而生&#xff0c;它…

    java中Reflection反射(一)

    目錄 一、概述 二、class類&#xff1a; 1、獲取類的字節碼文件&#xff1a; &#xff08;1&#xff09;方式一&#xff1a;直接通過一個class的靜態變量class獲取 &#xff08;2&#xff09;方式二&#xff1a;如果知道一個class的完整類名&#xff0c;可以通過靜態方法Cl…

    CVE-2021-1879

    一、漏洞原理 CVE-2021-1879 是 IBM WebSphere Application Server 中存在的一個 路徑遍歷&#xff08;Path Traversal&#xff09; 漏洞&#xff0c;其核心原理為&#xff1a; ①WebSphere 在處理某些文件操作請求&#xff08;如下載、上傳或配置文件讀取&#xff09;時&#…

    二進制簽名查找器(Aho-Corasick 自動機):設計思路與實現原理(C/C++代碼實現)

    在逆向工程、惡意軟件分析和二進制文件解析領域&#xff0c;快速準確地識別特定字節模式&#xff08;即“簽名”&#xff09;是一項核心任務。本文將圍繞一款基于PE-bear工具的二進制簽名查找器&#xff0c;深入解析其設計思路、實現原理及相關技術背景&#xff0c;揭示其如何高…

    後端開發技術教學(二) 條件指令、循環結構、定義函數

    書接上回&#xff1a;後端開發技術教學(一) [附2025最新可用 phpstudy2018下載鏈接] -CSDN博客 必要資源&#xff1a; trae中文版下載網址: TRAE - The Real AI Engineer phpStudy 2018 : phpStudy - Windows 一鍵部署 PHP 開發環境 小皮出品 目錄 一、條件指令 1.1 if() …

    狀壓DP-基本框架

    狀壓DP-基本框架一、狀壓DP的核心思想與適用場景1.1 問題特征1.2 核心思想1.3 與傳統DP的對比二、位運算基礎&#xff1a;狀壓DP的語法三、狀壓DP的基本框架3.1 步驟拆解3.2 通用代碼模板四、經典案例詳解4.1 旅行商問題&#xff08;TSP&#xff09;問題描述狀壓DP設計代碼實現…

    Web 端 AI 圖像生成技術的應用與創新:虛擬背景與創意圖像合成

    隨著 Stable Diffusion、Midjourney 等生成式 AI 模型的爆發,Web 端圖像生成技術從“實驗室demo”走向“工業化應用”。其中,虛擬背景替換(如視頻會議的動態背景生成)和創意圖像合成(如用戶上傳素材與 AI 生成元素的融合)成為最具代表性的場景,它們通過“文本描述→AI 生…

    應急響應知識總結

    應急響應 Windows系統 查賬號 1、查看服務器是否有弱口令&#xff0c;遠程管理端口是否對公網開放。 檢查方法&#xff1a;據實際情況咨詢相關服務器管理員。 2、查看服務器是否存在可疑賬號、新增賬號。 檢查方法&#xff1a;打開 cmd 窗口&#xff0c;輸入 lusrmgr.msc …

    智慧水務賦能二次供水管理精細化轉型:物聯網驅動的全鏈路解決方案

    隨著我國城鎮化率激增&#xff0c;高層建筑占比上升&#xff0c;二次供水系統已成為保障城市供水安全的核心環節。然而&#xff0c;傳統管理模式面臨設備老化、運維粗放、監管缺失等矛盾&#xff0c;在此背景下&#xff0c;《“十四五”節水型社會建設規劃》明確要求推進二次供…

    tsmc 5nm lvs之 short難搞的類型

    1、M3層以上的層次發生的short&#xff0c;dengsity很高的情況下&#xff0c;兩根信號net導致的short&#xff0c;刪除其中一根然后ecoRoute fix不掉的情況下&#xff0c;該怎么辦&#xff0c;可以嘗試去cut 周圍或者上方的power。 2、M1&#xff0c; M2由于cell 內部出pin&…

    初識神經網絡01——認識PyTorch

    文章目錄一、認識PyTorch1.1 PyTorch是什么1.2 安裝PyTorch二、認識Tensor2.1 創建Tensor2.1.1 基本方式2.2.2 創建線性和隨機張量2.2 Tensor屬性2.2.1 切換設備2.2.2 類型轉換2.3 Tensor與Numpy的數據轉換2.3.1 張量轉ndarray2.3.2 Numpy轉張量2.4 Tensor常見操作2.4.1 取值2.…

    Android UI 組件系列(十一):RecyclerView 多類型布局與數據刷新實戰

    博客專欄&#xff1a;Android初級入門UI組件與布局 源碼&#xff1a;通過網盤分享的文件&#xff1a;Android入門布局及UI相關案例 鏈接: https://pan.baidu.com/s/1EOuDUKJndMISolieFSvXXg?pwd4k9n 提取碼: 4k9n 引言 在 Android 應用中&#xff0c;RecyclerView 是最常用…

    如何學習跨模態對齊(尤其是 CLIP 思想)

    學習跨模態對齊&#xff08;尤其是CLIP思想&#xff09;需要結合理論基礎、經典模型原理、實踐復現和前沿擴展&#xff0c;以下是一套系統的學習路徑&#xff0c;從入門到深入逐步展開&#xff1a; 一、先補基礎&#xff1a;跨模態對齊的“前置知識” 跨模態對齊的核心是讓圖…

    日記研究:一種深入了解用戶真實體驗的UX研究方法

    在用戶體驗&#xff08;UX&#xff09;研究中&#xff0c;我們常常需要了解用戶在真實世界中如何與產品互動。然而&#xff0c;由于時間和空間的限制&#xff0c;我們很難像“特工”一樣全天候跟蹤用戶。這時&#xff0c;“日記研究”&#xff08;Diary Studies&#xff09;就成…