性能瓶颈分析
本文档系统性地介绍 MT GPU 应用程序的性能瓶颈分析方法,涵盖瓶颈识别、诊断技术和优化方案。
note
下文的数字仅做示例参考。具体请以实际情况为准。
瓶颈概述
瓶颈分类
MUSA 应用中的性能瓶颈主要分为四类:
| 瓶颈类型 | 优化方向 |
|---|---|
| 计算瓶颈 | 优化算法、使用 Tensor Core |
| 内存瓶颈 | 优化访问模式、提高带宽利用率 |
| 延迟瓶颈 | 增加并行度、提高占用率 |
| 同步瓶颈 | 减少同步、异步执行 |
瓶颈识别流程
分析方法
- Moore Perf Compute
- Moore Perf System
- muPTI Profiler API
- Python 分析脚本
# 基础分析
mcu -o profile.json ./application
# 指定指标分析
mcu --metrics \
sm_efficiency,\
gld_throughput,\
gst_throughput,\
sm__throughput.avg.pct_of_peak_sustained \
-o profile.json ./application
详细用法: 性能分析工具
# 系统级别分析(CPU-GPU 协同)
msys -t musa --gpu-metrics-set=0 -o timeline ./application
# Kernel 级别分析
msys -t musa --kernel-exec -o kernel_report ./application
#include <mupti.h>
// muPTI Profiler 需要设置 Session 和 Config
// 详细用法请参阅 muPTI 开发者指南
MUptiResult result;
// 初始化 Profiler
result = muptiProfilerInitialize(NULL);
// 开始 Session
result = muptiProfilerBeginSession(NULL);
// 设置性能计数器配置
result = muptiProfilerSetConfig(NULL);
// 启用性能分析
result = muptiProfilerEnableProfiling(NULL);
// ... 运行 Kernel ...
// 禁用性能分析
result = muptiProfilerDisableProfiling(NULL);
// 结束 Session
result = muptiProfilerEndSession(NULL);
// 清理
result = muptiProfilerDeInitialize(NULL);
注意:muPTI Profiler API 需要通过 muptiProfilerSetConfig 配置性能计数器,具体计数器 ID 请参阅 muPTI API Reference。
#!/usr/bin/env python3
"""
MUSA 性能瓶颈分析脚本
"""
import subprocess
import json
import sys
def run_mcu():
"""运行 Moore Perf Compute 分析"""
print("Running Moore Perf Compute...")
result = subprocess.run([
'mcu',
'--output', 'profile.json',
'--metrics', 'sm_efficiency,gld_throughput,gst_throughput',
sys.argv[1]
], capture_output=True, text=True)
return result.returncode == 0
def analyze_bottleneck(profile_data):
"""分析瓶颈类型"""
sm_eff = profile_data.get('sm_efficiency', 0)
mem_throughput = profile_data.get('gld_throughput', 0) + \
profile_data.get('gst_throughput', 0)
print(f"\n=== Bottleneck Analysis ===")
print(f"MP Efficiency: {sm_eff:.1f}%")
print(f"Memory Throughput: {mem_throughput:.1f} GB/s")
if sm_eff > 80:
print("\n⚠️ 计算瓶颈(Compute-Bound)检测到")
print(" 建议:")
print(" - 使用张量核心(Tensor Core)进行矩阵运算")
print(" - 优化算法以提高算术强度")
elif mem_throughput > 400: # 假设阈值
print("\n⚠️ 内存瓶颈(Memory-Bound)检测到")
print(" 建议:")
print(" - 优化内存访问模式")
print(" - 使用共享内存存储频繁访问的数据")
print(" - 提高算术强度")
else:
print("\n✓ 未检测到明显瓶颈")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("用法:python analyze.py <application>")
sys.exit(1)
if run_mcu():
with open('profile.json') as f:
profile_data = json.load(f)
analyze_bottleneck(profile_data)
计算瓶颈
识别特征
计算瓶颈应用程序的特征:
| 指标 | 特征 |
|---|---|
| MP 效率 | 高(大于 80%) |
| 内存吞吐量 | 低(小于 50% 峰值) |
| 算术强度 | 高 |
| 浮点运算吞吐量 | 高 |
诊断代码
// 计算强度分析
void analyzeComputeBound(const void* kernel) {
// 获取设备计算能力
int computeCapability;
musaDeviceGetAttribute(&computeCapability,
musaDevAttrComputeCapabilityMajor, 0);
int registersPerMP;
musaDeviceGetAttribute(®istersPerMP,
musaDevAttrRegistersPerMultiprocessor, 0);
// 理论最大 GFLOPS
float peakGFLOPS = 0.0f;
if (computeCapability 大于等于 8) {
int smCount;
musaDeviceGetAttribute(&smCount,
musaDevAttrMultiProcessorCount, 0);
// FP32: 每个 MP 每周期 256 FLOPs (假设)
peakGFLOPS = smCount * 1.5f * 1000; // 简化计算
}
printf("Peak GFLOPS: %.1f\n", peakGFLOPS);
// 占用率分析
int blockSize = 256;
int maxActiveBlocks;
musaOccupancyMaxActiveBlocks(&maxActiveBlocks, kernel,
blockSize, 0);
printf("Active blocks: %d\n", maxActiveBlocks);
}
优化策略
使用张量核心(Tensor Core):
// 使用 Tensor Core 进行矩阵乘法
#include <musa_tensor.h>
__global__ void tensorCoreGemm(half* a, half* b, float* c,
int M, int N, int K) {
// 使用 warp 矩阵累加(Warp Matrix Multiply Accumulate)
// m, n: 矩阵维度
// k: 累加维度
const int Mtile = 16;
const int Ntile = 16;
const int Ktile = 16;
// 定义矩阵片段
wmma::fragment<wmma::matrix_a, Mtile, Ntile, Ktile, half, wmma::row_major> a_frag;
wmma::fragment<wmma::matrix_b, Mtile, Ntile, Ktile, half, wmma::col_major> b_frag;
wmma::fragment<wmma::accumulator, Mtile, Ntile, Ktile, float> c_frag;
// 初始化
wmma::fill_fragment(c_frag, 0.0f);
// 矩阵乘累加
for (int k = 0; k < K; k += Ktile) {
wmma::load_matrix_sync(a_frag, a + k, K);
wmma::load_matrix_sync(b_frag, b + k * N, N);
wmma::mma_sync(c_frag, a_frag, b_frag, c_frag);
}
// 存储结果
wmma::store_matrix_sync(c, c_frag, N, wmma::mem_row_major);
}
算法优化:
// 优化: 使用查找表代替复杂计算
__device__ float fastLog2(float x) {
// 查找表大小
const int LUT_SIZE = 256;
__shared__ float lut[LUT_SIZE];
int tid = threadIdx.x;
if (tid < LUT_SIZE) {
// 预计算 log2 值
lut[tid] = log2f(1.0f + tid / (float)LUT_SIZE);
}
__syncthreads();
// 使用 LUT
int idx = (int)(x * LUT_SIZE);
idx = min(idx, LUT_SIZE - 1);
return lut[idx];
}
内存瓶颈
识别特征
内存瓶颈应用程序的特征:
| 指标 | 特征 |
|---|---|
| 内存吞吐量 | 接近峰值 |
| MP 效率 | 中等(小于等于 60%) |
| 算术强度 | 低(小于等于 10 FLOPs/Byte) |
| L2 缓存命中率 | 低 |
诊断代码
void diagnoseMemoryBound() {
// 获取 内存带宽
size_t globalMem;
musaDeviceGetAttribute(&globalMem,
musaDevAttrTotalGlobalMem, 0);
int memClockKHz;
musaDeviceGetAttribute(&memClockKHz,
musaDevAttrMemoryClockRate, 0);
int memBusWidth;
musaDeviceGetAttribute(&memBusWidth,
musaDevAttrMemoryBusWidth, 0);
// 理论峰值带宽 (GB/s)
float peakBandwidth = (memClockKHz / 1000.0f) *
(memBusWidth / 8.0f) * 2.0f;
printf("Peak memory bandwidth: %.1f GB/s\n", peakBandwidth);
// 使用 Nsight 测量实际带宽
// ncu --metrics l1tex__t_sectors_pipe_lsu_mem_global_op_ld.sum
}
优化策略
优化访问模式:
// 优化前:内存带宽受限
```c
// 优化前: 内存带宽受限
__global__ void memoryBoundKernel(float* data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
// 每次只读取 4 字节,计算很少
data[idx] = data[idx] * 0.0f; // 算术强度低
}
}
// 优化后: 增加算术强度
__global__ void computeBoundKernel(float* data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
float val = data[idx];
// 增加计算密度
#pragma unroll 8
for (int i = 0; i < 8; i++) {
val = val * 1.01f + 0.5f;
val = sinf(val);
}
data[idx] = val;
}
}
使用共享内存:
// 使用共享内存减少全局内存访问
__global__ void sharedMemoryKernel(float* globalData, float* result, int n) {
__shared__ float cache[256];
int tid = threadIdx.x;
int idx = blockIdx.x * blockDim.x + tid;
// 加载数据到共享内存
if (idx < n) {
cache[tid] = globalData[idx];
}
__syncthreads();
// 在共享内存上进行多次计算
float val = cache[tid];
#pragma unroll 4
for (int i = 0; i < 4; i++) {
val += cache[(tid + i) % 256];
}
// 写入结果
if (idx < n) {
result[idx] = val;
}
}
合并访问:
// 优化: 合并内存访问
__global__ void coalescedKernel(float* data, int width, int height) {
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;
if (x < width && y < height) {
// 合并访问: x 是最内层维度
int idx = y * width + x;
data[idx] = compute(data[idx]);
}
}
延迟瓶颈
识别特征
延迟瓶颈应用程序的特征:
| 指标 | 特征 |
|---|---|
| 每 MP 活跃线程束数 | 低 |
| 实际占用率 | 低 (小于30%) |
| 分支发散 | 高 |
| 内存延迟 | 无法隐藏 |
诊断代码
void diagnoseLatencyBound(const void* kernel) {
int maxThreadsPerMP;
musaDeviceGetAttribute(&maxThreadsPerMP,
musaDevAttrMaxThreadsPerMultiProcessor, 0);
printf("Max threads per MP: %d\n", maxThreadsPerMP);
// 尝试不同的块大小
for (int blockSize = 64; blockSize <= 1024; blockSize += 64) {
int maxActiveBlocks;
musaOccupancyMaxActiveBlocks(&maxActiveBlocks, kernel,
blockSize, 0);
int threadsPerBlock = blockSize;
int activeThreads = maxActiveBlocks * threadsPerBlock;
float occupancy = (float)activeThreads / (float)maxThreadsPerMP;
printf("Block size: %d, Active blocks: %d, Occupancy: %.1f%%\n",
blockSize, maxActiveBlocks, occupancy * 100.0f);
}
}
// Warp 效率分析
void analyzeWarpEfficiency(const void* kernel) {
// 使用 Nsight 分析
// ncu --metrics sm__average_warp_execution_efficiency
}
优化策略
增加并行度:
// 优化: 增加活跃 warp 数量
__global__ void highOccupancyKernel(float* data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
// 简单计算
data[idx] = data[idx] * 2.0f;
}
}
// 增加线程块数量提高占用率
// 优化配置
int blockSize = 256;
int gridSize = (n + blockSize - 1) / blockSize;
// 动态计算最佳配置
void optimizeGridSize(const void* kernel, int n) {
int maxActiveBlocks;
int blockSize = 256;
musaOccupancyMaxActiveBlocks(&maxActiveBlocks, kernel,
blockSize, 0);
// 使用所有可用块
int mpCount;
musaDeviceGetAttribute(&mpCount,
musaDevAttrMultiProcessorCount, 0);
int totalBlocks = maxActiveBlocks * mpCount;
int optimalGridSize = (n + blockSize - 1) / blockSize;
optimalGridSize = min(optimalGridSize, totalBlocks);
kernel<<<optimalGridSize, blockSize>>>(data, n);
}
减少分支分歧:
// 优化前: 高分支分歧
__global__ void divergentBranch(float* data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
// 随机分支导致分歧
if (data[idx] > 0.5f) {
data[idx] = sqrtf(data[idx]);
} else {
data[idx] = data[idx] * 2.0f;
}
}
}
// 优化后: 减少分歧
__global__ void nonDivergentBranch(float* data, int n) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n) {
float val = data[idx];
// 使用掩码操作而不是分支
float result1 = val * 2.0f;
float result2 = sqrtf(val);
// 无分支选择
bool flag = (val > 0.5f);
data[idx] = flag ? result2 : result1;
}
}
同步瓶颈
识别特征
| 指标 | 特征 |
|---|---|
| 流利用率 | 低 |
__syncthreads() 调用 | 频繁 |
| 核函数间依赖 | 多 |
| GPU-CPU 同步 | 频繁 |
诊断代码
// 分析同步开销
void analyzeSynchronization() {
musaEvent_t start, stop;
musaEventCreate(&start);
musaEventCreate(&stop);
musaStream_t stream;
musaStreamCreate(&stream);
// 测量核函数时间
musaEventRecord(start, stream);
kernel1<<<grid, block, 0, stream>>>(d_data1);
musaEventRecord(stop, stream);
float kernelTime;
musaEventElapsedTime(&kernelTime, start, stop);
// 测量同步时间
auto startSync = std::chrono::high_resolution_clock::now();
musaStreamSynchronize(stream);
auto endSync = std::chrono::high_resolution_clock::now();
printf("Kernel time: %.3f ms\n", kernelTime);
printf("Synchronization overhead: %.3f ms\n",
std::chrono::duration<float, std::milli>(endSync - startSync).count());
}