核心发现:通过逐步优化,矩阵乘法性能可提升 17倍以上
| 优化方法 | 峰值性能 | 相对基准 | 关键技术 |
|---|---|---|---|
| 单线程循环重排 (ikj) | 31.66 GFLOPs | 1x (基准) | 缓存局部性 |
| 单线程分块 | 31.45 GFLOPs | 1x | 分块 + 缓存优化 |
| OpenMP 简单并行 | 5.31 GFLOPs | 0.17x (更慢!) | 缓存竞争导致性能下降 |
| OpenMP + 分块 | 77.01 GFLOPs | 2.4x | 多线程 + 缓存优化 |
| OpenBLAS | 538.35 GFLOPs | 17x | AVX-512 + 专业优化 |
矩阵乘法 C = A × B,其中 A[M×K], B[K×N], C[M×N]:
CPU 缓存是按行(连续内存)加载的。不同循环顺序导致不同的内存访问模式:
| 循环顺序 | B矩阵访问 | 缓存效率 |
|---|---|---|
| ijk (朴素) | 按列跳跃访问 | 差 - 每次访问都 cache miss |
| ikj (优化) | 按行连续访问 | 好 - 利用缓存行 |
将大矩阵分成小块,使每个块能完全放入 L1/L2 缓存:
使用 #pragma omp parallel for 将工作分配给多个线程:
# 基础版本gcc -O3 -march=native -o matrix matrix.c -lm# OpenMP 多线程版本gcc -O3 -fopenmp -march=native -o matrix matrix.c -lm# OpenBLAS 版本 (需要先安装: sudo apt install libopenblas-dev)gcc -O3 -fopenmp -march=native -DUSE_OPENBLAS -o matrix matrix.c -lopenblas -lm
# 默认 1024x1024./matrix# 指定维度./matrix 2048# 指定 M K N (非方阵)./matrix 1024 512 2048
void matmul_naive(const float* A, const float* B, float* C, int M, int K, int N) {
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
float sum = 0.0f;
for (int k = 0; k < K; k++) {
sum += A[i * K + k] * B[k * N + j]; // B 按列访问,缓存不友好
}
C[i * N + j] = sum;
}
}
}
void matmul_ikj(const float* A, const float* B, float* C, int M, int K, int N) {
memset(C, 0, M * N * sizeof(float));
for (int i = 0; i < M; i++) {
for (int k = 0; k < K; k++) {
float a_ik = A[i * K + k]; // 提取到寄存器
for (int j = 0; j < N; j++) {
C[i * N + j] += a_ik * B[k * N + j]; // B 按行访问,缓存友好
}
}
}
}
#define BLOCK_SIZE 64 // 块大小,通常选择 L1 缓存大小的 1/3
void matmul_blocked(const float* A, const float* B, float* C, int M, int K, int N) {
memset(C, 0, M * N * sizeof(float));
for (int i0 = 0; i0 < M; i0 += BLOCK_SIZE) {
for (int k0 = 0; k0 < K; k0 += BLOCK_SIZE) {
for (int j0 = 0; j0 < N; j0 += BLOCK_SIZE) {
// 块内乘法
int i_end = (i0 + BLOCK_SIZE < M) ? i0 + BLOCK_SIZE : M;
int k_end = (k0 + BLOCK_SIZE < K) ? k0 + BLOCK_SIZE : K;
int j_end = (j0 + BLOCK_SIZE < N) ? j0 + BLOCK_SIZE : N;
for (int i = i0; i < i_end; i++) {
for (int k = k0; k < k_end; k++) {
float a_ik = A[i * K + k];
for (int j = j0; j < j_end; j++) {
C[i * N + j] += a_ik * B[k * N + j];
}
}
}
}
}
}
}
#include <omp.h>
void matmul_omp(const float* A, const float* B, float* C, int M, int K, int N) {
memset(C, 0, M * N * sizeof(float));
#pragma omp parallel for schedule(dynamic) // 动态调度,负载均衡
for (int i0 = 0; i0 < M; i0 += BLOCK_SIZE) {
for (int k0 = 0; k0 < K; k0 += BLOCK_SIZE) {
for (int j0 = 0; j0 < N; j0 += BLOCK_SIZE) {
// ... 块内计算同上
}
}
}
}
#include <cblas.h>
void matmul_openblas(const float* A, const float* B, float* C, int M, int K, int N) {
// C = alpha * A * B + beta * C
cblas_sgemm(CblasRowMajor, // 行优先存储
CblasNoTrans, // A 不转置
CblasNoTrans, // B 不转置
M, N, K, // 矩阵维度
1.0f, // alpha = 1
A, K, // A 矩阵
B, N, // B 矩阵
0.0f, // beta = 0
C, N); // C 矩阵
}
| 项目 | 配置 |
|---|---|
| CPU | 20 核心 (OpenMP 线程数: 20) |
| 矩阵大小 | 1024 × 1024 (单精度 float) |
| 总浮点运算 | 2.15 GFLOPs |
| 内存占用 | 12.00 MB |
单线程优化效果:
OpenMP 简单并行为什么更慢?
OpenMP + 分块为什么有效?
OpenBLAS 为什么快这么多?
| 技术 | 预期性能 | 说明 |
|---|---|---|
| Intel MKL | ~600 GFLOPs | Intel CPU 上比 OpenBLAS 更快 10-30% |
| GPU (cuBLAS) | 30,000-80,000 GFLOPs | RTX 4090 可达 ~80 TFLOPs (FP32) |
| FP16 半精度 | 2x FP32 | 精度降低换取速度,深度学习常用 |
| Tensor Core | 160,000+ GFLOPs | NVIDIA GPU 专用矩阵加速单元 |
| 分布式计算 | 线性扩展 | MPI + 多机协同 |
#include <cublas_v2.h>
// GPU 矩阵乘法
void matmul_gpu(const float* A, const float* B, float* C, int M, int K, int N) {
cublasHandle_t handle;
cublasCreate(&handle);
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, M * K * sizeof(float));
cudaMalloc(&d_B, K * N * sizeof(float));
cudaMalloc(&d_C, M * N * sizeof(float));
cudaMemcpy(d_A, A, M * K * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(d_B, B, K * N * sizeof(float), cudaMemcpyHostToDevice);
float alpha = 1.0f, beta = 0.0f;
cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N,
N, M, K, &alpha, d_B, N, d_A, K, &beta, d_C, N);
cudaMemcpy(C, d_C, M * N * sizeof(float), cudaMemcpyDeviceToHost);
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
cublasDestroy(handle);
}
实际工程建议:
不同加速硬件有各自独立的编程接口,CUDA 仅适用于 NVIDIA GPU,不能用于 TPU 或 NPU:
| 硬件 | 编程接口 | 厂商 | 是否支持 CUDA |
|---|---|---|---|
| NVIDIA GPU | CUDA / cuBLAS | NVIDIA | 支持 (原生) |
| AMD GPU | ROCm / HIP | AMD | 不支持 (HIP 可移植) |
| Google TPU | XLA / JAX | 不支持 | |
| NPU | CANN / CoreML / SNPE | 华为 / Apple / 高通 | 不支持 |
| Intel GPU | oneAPI / SYCL | Intel | 不支持 |
测试环境: Ubuntu 22 / GCC / 20核 CPU
生成时间: 2026-01-29