内存对齐没做会导致_mm256_load_ps崩溃或静默降级:avx2要求float*地址被32整除,否则部分cpu触发#gp异常,另一些自动退化为标量路径致性能下降3–5倍;必须用_mm_malloc(size, 32)或aligned_alloc(32, size)分配内存,并验证(uintptr_t)ptr & 31恒为0。

内存对齐没做,_mm256_load_ps直接崩溃或静默降级
AVX2 要求 float* 地址能被 32 整除,否则 _mm256_load_ps 在部分 CPU 上触发 #GP 异常,另一些则自动退化成标量路径——性能掉 3–5 倍,还难复现。
- 用
new float[n]或std::vector<float></float>分配的内存几乎都不满足条件 - 必须用
_mm_malloc(size, 32)分配,_mm_free(ptr)释放;aligned_alloc(32, size)也可,但要确保size % 32 == 0 - 验证对齐:打印
(uintptr_t)ptr & 31,结果必须恒为0 - 矩阵维度(如
K)建议按 8 或 16 向上补齐,避免边界分支打乱流水线
不分块就写三层循环,L1d 缓存命中率低于 30%
不加任何分块的手写 AVX 矩阵乘,每算一个 C[i][j] 都要重载整行 A 和整列 B,而 B 的列访问是跨步的,L1d 行反复被踢出。实测 miss rate 超过 70%,CPU 大部分时间在等数据,不是在算。
- 至少做两级分块:外层按
MC×KC子矩阵驻留L1,内层按8×8tile 计算 -
A保持行主序,B推荐预转置成行主序(或现场按列访问时用_mm256_i32gather_ps+ shuffle,但更慢) - 每个 tile 内,
A数据只加载一次,B每 8 个元素复用一次,C累加进寄存器再回写
不手动 _mm_prefetch,AVX 吞吐率掉一半
硬件预取器对 B 的列访问基本失效。L2→L1 延迟约 12–15 cycle,而 _mm256_fmadd_ps 只需 1–2 cycle,空等占大头。不做预取,再好的分块也白搭。
- 计算当前
A块前,用_mm_prefetch(&A[i + 64], _MM_HINT_NTA)提前拉取后续 64 字节(2 个 AVX 向量) - 对
B,沿列方向每隔 8 行预取一次:_mm_prefetch(&B[(t + 8) * ldb + j], _MM_HINT_NTA) - 统一用
_MM_HINT_NTA:避免污染 cache,适合只读一次的大块数据
transA/transB 和 batch 场景硬塞进一个函数,指针偏移必错
深度学习里 matmul 很少是纯 A×B,更多是 B×AT 或 batched[b, m, k] × [b, k, n]。用 if-else 切换 layout,容易错在 ldA、ldB、指针偏移和 stride 上,一跑就越界或结果全零。
- 定义统一接口:
simd_gemm(bool transA, bool transB, int M, int N, int K, const float* A, int ldA, ...) -
transpose不现场转,改访问模式:transA ? A[j * ldA + i] : A[i * ldA + j] -
batch场景下,外层循环跑次,每次调用前更新指针:A += b_stride、B += b_stride、C += b_stride
真正卡住人的从来不是指令怎么写,而是对齐没验、分块尺寸拍脑袋、预取位置偏移算错——这些地方一错,性能差几倍都看不出明显报错,只能靠 (uintptr_t)ptr & 31 和 perf stat 看 cache-misses 才能揪出来。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











