性能瓶颈分析
本文档系统性地介绍 MT GPU 应用程序的性能瓶颈分析方法,涵盖瓶颈识别、诊断技术和优化方案。
备注
下文的数字仅做示例参考。具体请以实际情况为准。
瓶颈概述
瓶颈分类
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 进行矩阵乘法
__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);
}
MP31 的完整计算接口列表、shape、数据类型和调用约束请参阅 TCE API Reference - PH1; 数据搬运和异步流水接口请参阅 TME API Reference - PH1。
算法优化:
// 优化: 使用查找表代替复杂计算
__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());
}
优化策略
减少 __syncthreads():
// 优化前: 多次同步
__global__ void excessiveSync(float* data) {
__shared__ float cache[256];
int tid = threadIdx.x;
// 第一阶段
cache[tid] = data[tid];
__syncthreads();
// 计算
if (tid < 128) cache[tid] += cache[tid + 128];
__syncthreads();
// 第二阶段
if (tid < 64) cache[tid] += cache[tid + 64];
__syncthreads();
// 第三阶段
if (tid < 32) cache[tid] += cache[tid + 32];
if (tid == 0) data[0] = cache[0];
}
// 优化后: 减少同步
__global__ void reducedSync(float* data) {
__shared__ float cache[256];
int tid = threadIdx.x;
// 预加载
cache[tid] = data[tid];
__syncthreads();
// 树形归约,只需 log2(256) = 8 次同步
for (int s = 1; s < 256; s *= 2) {
if (tid < 256 / (2 * s))
cache[tid] += cache[tid + s * 128 / 256 * 128 / (2 * s)];
__syncthreads();
}
if (tid == 0) data[0] = cache[0];
}
使用异步操作:
// 异步拷贝和计算重叠
void overlapTransferCompute(float* h_data, float* d_data,
size_t size, musaStream_t stream) {
// 异步拷贝
musaMemcpyAsync(d_data, h_data, size,
musaMemcpyHostToDevice, stream);
// 核函数与拷贝并行
kernel<<<grid, block, 0, stream>>>(d_data);
// 结果回传
musaMemcpyAsync(h_data, d_data, size,
musaMemcpyDeviceToHost, stream);
}
综合分析工具
性能数据收集脚本
#!/bin/bash
# 性能数据收集脚本
echo "=== MUSA Performance Analysis ==="
# 基本信息
echo "Device Info:"
mu-info
# 占用率分析
echo "Occupancy Analysis:"
mcu --metrics sm__achieved_occupancy.pct ./application
# 内存分析
echo "Memory Analysis:"
mcu --metrics gld_throughput,gst_throughput ./application
# 计算分析
echo "Compute Analysis:"
mcu --metrics sm__throughput.avg.pct_of_peak_sustained ./application
# Warp 效率
echo "Warp Efficiency:"
mcu --metrics sm__average_warp_execution_efficiency ./application
优化决策树
决策说明:
| 条件组合 | 瓶颈类型 | 优化方向 |
|---|---|---|
| MP 效率 > 80%,内存吞吐量 > 80% | 计算瓶颈 | 优化算法、使用 Tensor Core |
| MP 效率 > 80%,内存吞吐量 < 80% | 延迟瓶颈 | 增加指令级并行、减少分支发散 |
| MP 效率 < 80%,内存吞吐量 > 80% | 内存瓶颈 | 优化访问模式、提高带宽利用率 |
| MP 效率 < 80%,内存吞吐量 < 80% | 占用率不足 | 增加并行度、优化线程块配置 |
常见瓶颈案例
案例 1: 矩阵转置
问题分析:
- MP 效率:45%
- 内存读取:80% 峰值
- 内存写入:80% 峰值
- 结论:内存瓶颈(写冲突)
解决方案:
// 使用共享内存优化
__global__ void optimizedTranspose(float* output, float* input,
int width, int height) {
__shared__ float tile[16][16 + 1]; // +1 避免 bank 冲突
int x = blockIdx.x * 16 + threadIdx.x;
int y = blockIdx.y * 16 + threadIdx.y;
// 读取
if (x < width && y < height) {
tile[threadIdx.y][threadIdx.x] = input[y * width + x];
}
__syncthreads();
// 写入
x = blockIdx.y * 16 + threadIdx.x;
y = blockIdx.x * 16 + threadIdx.y;
if (x < height && y < width) {
output[y * height + x] = tile[threadIdx.x][threadIdx.y];
}
}
案例 2: 归约操作
问题分析:
- MP 效率:30%
- 每 MP 活跃线程束:4(共 16 个)
- 结论:延迟瓶颈(占用率低)
解决方案:
// 增加并行度
__global__ void parallelReduction(float* data, float* result, int n) {
__shared__ float cache[256];
int tid = threadIdx.x;
int idx = blockIdx.x * blockDim.x + tid;
// 每个线程处理多个元素
float sum = 0.0f;
for (int i = idx; i < n; i += blockDim.x * gridDim.x) {
sum += data[i];
}
cache[tid] = sum;
__syncthreads();
// 归约
for (int s = blockDim.x / 2; s > 0; s >>= 1) {
if (tid < s) {
cache[tid] += cache[tid + s];
}
__syncthreads();
}
if (tid == 0) {
atomicAdd(result, cache[0]);
}
}
优化效果验证
性能基准测试
void benchmarkOptimization(float* d_data, int n,
void (*kernel)(float*, int),
const char* name) {
musaEvent_t start, stop;
musaEventCreate(&start);
musaEventCreate(&stop);
const int iterations = 100;
float totalTime = 0.0f;
// 预 热
for (int i = 0; i < 10; i++) {
kernel<<<gridSize, blockSize>>>(d_data, n);
}
musaDeviceSynchronize();
// 测量
for (int i = 0; i < iterations; i++) {
musaEventRecord(start);
kernel<<<gridSize, blockSize>>>(d_data, n);
musaEventRecord(stop);
musaEventSynchronize(stop);
float ms;
musaEventElapsedTime(&ms, start, stop);
totalTime += ms;
}
printf("%s: %.3f ms (avg of %d runs)\n",
name, totalTime / iterations, iterations);
}
优化前后对比
| 优化项 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 内存访问 | 45 GB/s | 380 GB/s | 8.4x |
| MP 占用率 | 35% | 85% | 2.4x |
| 执行时间 | 12.5 ms | 2.1 ms | 6.0x |
| 带宽利用率 | 15% | 95% | 6.3x |

