性能远不如OpenBLAS sgemm,因瓶颈在内存对齐、cache分块、预取与数据复用;需32字节对齐分配、多级分块、显式预取及布局适配。

直接用 _mm256_mul_ps 写个三层循环做矩阵乘,性能大概率不如 OpenBLAS 的 sgemm —— 瓶颈根本不在计算单元,而在内存对齐、cache 行填充、预取节奏和数据重用效率。SIMD 指令只是工具,不是银弹。
内存必须 32 字节对齐,否则 _mm256_load_ps 会降级或崩溃
AVX 指令要求操作的 float 数组地址是 32 字节对齐的。用 new float[n] 或 std::vector<float></float> 默认分配的内存几乎一定不满足——它只保证 16 字节对齐(甚至更低)。一旦传给 _mm256_load_ps,在某些 CPU 上触发 #GP 异常,在另一些上 silently 降级为慢速路径,性能断崖式下跌。
实操建议:
- 用
_mm_malloc(size, 32)分配内存,用_mm_free(ptr)释放;aligned_alloc(32, size)也可,但需确保size % 32 == 0 - 矩阵维度(如
K)最好按 8(AVX)或 16(AVX-512)向上补齐,避免边界处理分支打乱流水线 - 验证对齐:打印
(uintptr_t)ptr & 31,结果应恒为 0
必须分块(blocking),否则 L1d cache 命中率低于 30%
不加任何分块的手写 SIMD 乘法,典型表现是每计算一个 C[i][j] 就要从内存重新加载整行 A 和整列 B —— 列 B 还是跨步访问,cache 行反复被踢出。实测 L1d miss rate 超过 70%,算力全耗在等数据。
立即学习“C++免费学习笔记(深入)”;
OpenBLAS 的 sgemm 采用三级分块:MC×KC 子矩阵驻留 L1、NC×KC 驻留 L2、外层 MR×NR 循环复用寄存器。你自己实现至少得做两级:
- 内层按 8×8 或 16×8 tile 处理 C 的一块,让 A 的一行块、B 的一列块尽可能留在 L1
- 对 A 使用行主序,对 B 使用列主序(或转置后行主序),减少跨步访问
- 每个 tile 计算中,A 数据只加载一次,B 数据按 8 元素一组循环复用,C 累加进 AVX 寄存器再回写
不手动预取(_mm_prefetch),AVX 吞吐率掉一半
即使做了分块,若不显式预取下一块数据,CPU 的硬件预取器对非规则访存(如 B 的列访问)基本失效。L2→L1 的延迟约 12–15 cycle,而 AVX 乘加单条指令才 1–2 cycle,空等占大头。
实操建议:
- 在计算当前 A 块前,用
_mm_prefetch(&A[i + 64], _MM_HINT_NTA)提前拉取后续 64 字节(2 个 AVX float32 向量) - 对 B,沿列方向每隔 8 行预取一次:比如当前处理
B[t * ldb + j],就预取&B[(t+8) * ldb + j] - 用
_MM_HINT_NTA(non-temporal hint),避免污染 cache,尤其适合只用一次的大块数据
batch 和 transpose 场景别硬塞进一个函数,指针偏移比现场转置快 3×
深度学习里 matmul 几乎从不出现纯 A×B:常见的是 B×A^T(QK^T)、batched([b,m,k] × [b,k,n])。试图在一个函数里用 if 分支判断转置,会让编译器无法向量化;现场转置 B 更是灾难——额外 O(b×k×n) 内存拷贝。
正确做法是把 layout 逻辑下沉到访存索引:
- 定义统一接口:
simd_gemm(bool transA, bool transB, int M, int N, int K, const float* A, int ldA, const float* B, int ldB, float* C, int ldC) - 访问 A 时:原
A[i * ldA + t]→transA ? A[t * ldA + i] : A[i * ldA + t],不改数据,只改读法 - batch 场景:外层 for b in range(BATCH),每次调用前更新指针:
A += b_stride(b_stride = M*K)、B += b_stride、C += M*N
真正卡住性能的,从来不是“会不会用 _mm256_madd_ps”,而是对 cache 层次、预取机制、内存一致性模型这些底层行为的感知是否准确。写完第一版后,用 perf stat -e cache-misses,cache-references,instructions,cycles 对比 sgemm,miss ratio > 5% 就说明分块或预取还没到位。


















