高级内存优化
备注
本篇文档的数字仅做示例参考。具体数据,请以实际情况为准。
本文档帮你深入理解 MUSA 高级内存特性:系统内存优化、L2 缓存管理、Cluster 内存和异步执行。
页锁定内存
分配和释放
#include <musa.h>
#include <stdio.h>
int main() {
size_t size = 1000000 * sizeof(float);
// 分配页锁定内存
float* h_pinned;
musaMallocHost(&h_pinned, size);
// 初始化数据
for (int i = 0; i < 1000000; i++) {
h_pinned[i] = float(i);
}
// 使用完成后释放
musaFreeHost(h_pinned);
return 0;
}
异步内存传输
void asyncTransfer(float* h_pinned, float* d_data, size_t size) {
musaStream_t stream;
musaStreamCreate(&stream);
// 异步 H2D 拷贝
musaMemcpyAsync(d_data, h_pinned, size,
musaMemcpyHostToDevice, stream);
// 启动 kernel(与传输重叠)
processKernel<<<grid, block, 0, stream>>>(d_data);
// 异步 D2H 拷贝
musaMemcpyAsync(h_pinned, d_data, size,
musaMemcpyDeviceToHost, stream);
// 等待完成
musaStreamSynchronize(stream);
musaStreamDestroy(stream);
}
流水线传输
void pipelineTransfer(float* h_input, float* h_output, int n) {
float *d_input, *d_output;
musaMalloc(&d_input, n * sizeof(float));
musaMalloc(&d_output, n * sizeof(float));
// 分配 pinned buffers
float *h_pinned_in, *h_pinned_out;
musaMallocHost(&h_pinned_in, n * sizeof(float));
musaMallocHost(&h_pinned_out, n * sizeof(float));
musaStream_t stream;
musaStreamCreate(&stream);
// 拷贝输入数据到 pinned buffer
memcpy(h_pinned_in, h_input, n * sizeof(float));
// 异步传输 + 计算流水线
musaMemcpyAsync(d_input, h_pinned_in, n,
musaMemcpyHostToDevice, stream);
processKernel<<<grid, block, 0, stream>>>(d_input, d_output);
musaMemcpyAsync(h_pinned_out, d_output, n,
musaMemcpyDeviceToHost, stream);
// 等待完成
musaStreamSynchronize(stream);
// 拷贝结果
memcpy(h_output, h_pinned_out, n * sizeof(float));
// 清理
musaFreeHost(h_pinned_in);
musaFreeHost(h_pinned_out);
musaFree(d_input);
musaFree(d_output);
musaStreamDestroy(stream);
}
可移植页锁定内存(零拷贝,Zero-Copy)
// 分配可移植 pinned memory(支持多 GPU)
float* h_mapped;
musaMallocHost(&h_mapped, size, musaHostAllocMapped);
// 获取设备指针
float* d_mapped;
musaHostGetDevicePointer(&d_mapped, h_mapped, 0);
// 直接在 kernel 中使用(零拷贝)
__global__ void zeroCopyKernel(float* data) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < 1000000) {
data[idx] = data[idx] * 2.0f;
}
}
zeroCopyKernel<<<grid, block>>>(d_mapped);
musaDeviceSynchronize();
musaFreeHost(h_mapped);
零拷贝内存
零拷贝基础
零拷贝允许 GPU 直接访问主机内存,无需显式拷贝:
#include <musa.h>
#include <stdio.h>
#define N 1000000
__global__ void zeroCopyKernel(float* data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
data[idx] = data[idx] * 2.0f + 1.0f;
}
}
int main() {
size_t size = N * sizeof(float);
// 分配 mapped pinned memory
float* h_data;
musaMallocHost(&h_data, size, musaHostAllocMapped);
// 获取设备指针
float* d_data;
musaHostGetDevicePointer(&d_data, h_data, 0);
// CPU 初始化
for (int i = 0; i < N; i++) {
h_data[i] = float(i);
}
// GPU 处理(直接访问主机内存)
dim3 blockSize(256);
dim3 gridSize((N + blockSize.x - 1) / blockSize.x);
zeroCopyKernel<<<gridSize, blockSize>>>(d_data, N);
musaDeviceSynchronize();
// CPU 读取结果(无需拷贝)
printf("Result[0] = %f\n", h_data[0]);
musaFreeHost(h_data);
return 0;
}
零拷贝性能考虑
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 大数据集 | Pinned Memory + 异步拷贝 | PCIe 带宽最大化 |
写合并内存(Write-Combined Memory)
分配 Write-Combined Memory
// 分配 write-combined memory(优化写入带宽)
float* h_wc;
musaMallocHost(&h_wc, size, musaHostAllocWriteCombined);
// 写入数据(高带宽)
for (int i = 0; i < N; i++) {
h_wc[i] = float(i);
}
// 拷贝到设备
musaMemcpy(d_data, h_wc, size, musaMemcpyHostToDevice);
musaFreeHost(h_wc);
性能对比
void compareMemoryTypes(int N) {
size_t size = N * sizeof(float);
float *d_data, *d_result;
musaMalloc(&d_data, size);
musaMalloc(&d_result, size);
// 方式 1: Pageable Memory
float* h_pageable = (float*)malloc(size);
for (int i = 0; i < N; i++) h_pageable[i] = float(i);
musaMemcpy(d_data, h_pageable, size, musaMemcpyHostToDevice);
// 带宽:~3 GB/s(受限于 PCIe + 临时 buffer 拷贝)
// 方式 2:锁定内存(Pinned Memory)
float* h_pinned;
musaMallocHost(&h_pinned, size);
for (int i = 0; i < N; i++) h_pinned[i] = float(i);
musaMemcpy(d_data, h_pinned, size, musaMemcpyHostToDevice);
// 带宽:~6-8 GB/s(直接 PCIe 传输)
// 方式 3:写合并内存(Write-Combined Memory)
float* h_wc;
musaMallocHost(&h_wc, size, musaHostAllocWriteCombined);
for (int i = 0; i < N; i++) h_wc[i] = float(i);
musaMemcpy(d_data, h_wc, size, musaMemcpyHostToDevice);
// 带宽:~8-10 GB/s(优化的写入路径)
// 清理
free(h_pageable);
musaFreeHost(h_pinned);
musaFreeHost(h_wc);
musaFree(d_data);
musaFree(d_result);
}
L2 缓存管理
L2 缓存是 GPU 内存层次结构中的关键组件,位于全局内存和更高层缓存(如 L1/共享内存)之间。
L2 缓存预留(L2 Set-aside)
预留部分 L2 缓存给特定数据,减少关键数据被驱逐:
#include <musa.h>
#include <stdio.h>
int main() {
// 查询 L2 缓存大小
musaDeviceProp_t prop;
int device;
musaGetDevice(&device);
musaGetDeviceProperties(&prop, device);
printf("L2 cache size: %d bytes\n", prop.l2CacheSize);
// 分配设备内存
size_t size = 1000000 * sizeof(float);
float* important_data;
musaMalloc(&important_data, size);
// 设置 L2 set-aside(预留 25% L2 缓存给该数据)
size_t reserve_size = prop.l2CacheSize / 4;
musaSetAttribute(important_data, musaAttributeL2CacheReserve,
&reserve_size, sizeof(reserve_size));
// 执行 kernel,important_data 更可能保留在 L2 中
kernel1<<<grid, block>>>(important_data);
musaFree(important_data);
return 0;
}
多内核共享数据
void multiKernelAccess(float* shared_data, size_t size) {
int device;
musaGetDevice(&device);
// 查询 L2 缓存大小
int l2_size;
musaDeviceGetAttribute(&l2_size, musaDeviceAttributeL2CacheSize, device);
// 预留 50% L2 缓存给共享数据
size_t reserve = l2_size / 2;
musaSetAttribute(shared_data, musaAttributeL2CacheReserve,
&reserve, sizeof(reserve));
// 多个 kernel 顺序执行,共享数据保留在 L2 中
kernel1<<<grid, block>>>(shared_data);
kernel2<<<grid, block>>>(shared_data);
kernel3<<<grid, block>>>(shared_data);
// 无需担心中间数据被驱逐
}
访问策略窗口(Access Policy Window)
对于只访问一次的数据,使用流式策略避免污染 L2 缓存:
// 设置流式访问策略
void streamingAccess(float* streaming_data, size_t size) {
musaAccessPolicyWindow policy;
policy.base_ptr = streaming_data;
policy.num_bytes = size;
policy.hitRatio = 0.0f; // 0% 命中率(只用一次)
policy.hitProp = musaAccessPropertyStreaming;
policy.missProp = musaAccessPropertyStreaming;
musaSetAttribute(streaming_data, musaAttributeAccessPolicyWindow,
&policy, sizeof(policy));
// 执行 kernel,数据不会污染 L2 缓存
streamingKernel<<<grid, block>>>(streaming_data);
}
重用数据策略
对于频繁重用的数据,设置重用策略:
// 设置重用策略
void reuseAccess(float* reuse_data, size_t size) {
musaAccessPolicyWindow policy;
policy.base_ptr = reuse_data;
policy.num_bytes = size;
policy.hitRatio = 1.0f; // 100% 命中率(频繁访问)
policy.hitProp = musaAccessPropertyReuse;
policy.missProp = musaAccessPropertyReuse;
musaSetAttribute(reuse_data, musaAttributeAccessPolicyWindow,
&policy, sizeof(policy));
// 多个 kernel 重复访问
for (int i = 0; i < 10; i++) {
processKernel<<<grid, block>>>(reuse_data);
}
}
混合访问模式
void mixedAccessPattern(float* lookup_table, float* streaming_input,
float* output, int N) {
size_t table_size = 256 * sizeof(float);
size_t input_size = N * sizeof(float);
// Lookup table:重用策略
musaAccessPolicyWindow table_policy;
table_policy.base_ptr = lookup_table;
table_policy.num_bytes = table_size;
table_policy.hitRatio = 1.0f;
table_policy.hitProp = musaAccessPropertyReuse;
table_policy.missProp = musaAccessPropertyReuse;
musaSetAttribute(lookup_table, musaAttributeAccessPolicyWindow,
&table_policy, sizeof(table_policy));
// 输入数据:流式策略
musaAccessPolicyWindow input_policy;
input_policy.base_ptr = streaming_input;
input_policy.num_bytes = input_size;
input_policy.hitRatio = 0.0f;
input_policy.hitProp = musaAccessPropertyStreaming;
input_policy.missProp = musaAccessPropertyStreaming;
musaSetAttribute(streaming_input, musaAttributeAccessPolicyWindow,
&input_policy, sizeof(input_policy));
// 执行 kernel
lookupKernel<<<grid, block>>>(lookup_table, streaming_input, output, N);
musaDeviceSynchronize();
}
缓存行优化
// 查询 L2 缓存行大小
int cache_line_size;
musaDeviceGetAttribute(&cache_line_size,
musaDeviceAttributeL2CacheLineSize, device);
printf("L2 cache line size: %d bytes\n", cache_line_size);
// 按缓存行对齐数据结构
struct alignas(128) CacheAlignedData {
float data[32]; // 128 bytes,对齐缓存行
};
Cluster 内存
Cluster 内存(分布式共享内存)允许不同线程块(Thread Block)的线程共享数据并通信。
Cluster Group 基础
#include <musa.h>
#include <cooperative_groups.h>
using namespace cooperative_groups;
__global__ void clusterExample(float* data, int N) {
// 获取 cluster group
cluster_group g = this_cluster();
// Cluster 信息
int cluster_rank = g.thread_rank(); // 在 cluster 中的排名
int cluster_size = g.size(); // cluster 总线程数
int num_clusters = g.num_clusters(); // cluster 数量
// Cluster 内线程 ID
int cluster_tid = g.thread_index();
if (cluster_rank == 0) {
// 只有 cluster 0 执行
data[0] = float(cluster_size);
}
// Cluster 内同步
g.sync();
}
Cluster 共享内存
// 使用 clusterS__musa_cluster_sync() 进行 Cluster 同步
extern "C" __device__ void clusterS__musa_cluster_sync(unsigned int clusterSize);
__global__ void clusterSyncExample() {
int clusterId = blockIdx.x;
int threadId = threadIdx.x;
// 每个线程初始化数据
float localData = float(threadId) * float(clusterId);
// 使用 clusterS__musa_cluster_sync() 同步
// 参数为 cluster 内的 block 数量
clusterS__musa_cluster_sync(gridDim.x);
// 同步后可以访问其他 block 的数据
}
线程块间通信
使用 Cluster 共享内存实现线程块间数据交换:
__global__ void blockCommunication(float* input, float* output, int N) {
clusterShared float cluster_buffer[1024];
int cluster_idx = threadIdx.x + blockIdx.x * blockDim.x;
// 步骤 1:写入本 block 数据
cluster_buffer[cluster_idx] = input[cluster_idx];
// Cluster 同步
__cluster_sync();
// 步骤 2:读取相邻 block 数据
int left_idx = cluster_idx - blockDim.x;
int right_idx = cluster_idx + blockDim.x;
float left_value = (blockIdx.x > 0) ? cluster_buffer[left_idx] : 0.0f;
float right_value = (blockIdx.x < 3) ? cluster_buffer[right_idx] : 0.0f;
// 计算(使用邻居数据)
output[cluster_idx] = left_value + cluster_buffer[cluster_idx] + right_value;
}