You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AVX矩阵乘法分块大小设置问题:块大小16时结果异常

AVX分块矩阵乘法块大小设为16时结果错误的原因

问题描述

我编写了基于AVX的分块矩阵乘法代码,当块大小设为4时运行正确,但改为16时,输出矩阵仅1/4的结果正确。已知x86有16个YMM寄存器,疑惑为何块大小不能设为16。

原始代码

#include <x86intrin.h>
#include <immintrin.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void fill(double *mat, int n) {
  register int i;
  register int j;
  for (i = 0; i < n; ++i) {
    for (j = 0; j < n; ++j) {
      mat[i * n + j] = (double) (i * n + j);
    }
  }
}

void blocked_mmul_avx(double *A, double *B, double *C, int n) {
  register int row, block, chunk, sub_chunk;
  int block_size = 4; // 修改为16时结果仅1/4正确

  for (row = 0; row < n; row++)
    for (block = 0; block < n; block += block_size) {
      register __m256d c0 = _mm256_setzero_pd();
      for (chunk = 0; chunk < n; chunk += block_size) {
        for (sub_chunk = 0; sub_chunk < block_size; sub_chunk++) {
          register __m256d a = _mm256_broadcast_sd(
              &A[row * n + chunk + sub_chunk]);
          register __m256d b = _mm256_loadu_pd(
              &B[chunk * n + sub_chunk * n + block]);
          c0 += a * b;
        }
      }
      _mm256_storeu_pd(&C[row * n + block], c0);
    }
}

int main() {
  int n = 4096; //n = 512, 1024, 2048, 4096

  double *A;
  double *B;
  double *C;

  A = (double*) malloc(n * n * sizeof(double));
  B = (double*) malloc(n * n * sizeof(double));
  C = (double*) malloc(n * n * sizeof(double));

  srand(0);
  fill(A, n);  // 用于为A、B加载数据的函数
  fill(B, n);

  printf("n = %d\n\n", n);
  clock_t t0 = clock();
  blocked_mmul_avx(A, B, C, n);
  clock_t t1 = clock();
  printf("Execution Time = %0.3f s \n\n", (float) (t1 - t0) / CLOCKS_PER_SEC);

  free(A);
  free(B);
  free(C);
  return 0;
}

原因分析

  • 单寄存器容量限制:__m256d是256位向量寄存器,每个最多容纳4个double类型元素(8字节×4=32字节=256位)。你的代码仅使用c0这一个寄存器累加结果,当block_size=16时,你试图一次性处理16列的计算,但c0只能存储4列的结果,剩余12列的C矩阵位置从未被正确赋值,因此仅前4列(即1/4)结果正确。
  • 寄存器数量与块大小的误解:16个YMM寄存器是处理器的硬件资源,但你的代码并未利用多寄存器并行处理多列结果。要支持block_size=16,需要对应使用4个__m256d寄存器(16÷4=4),每个寄存器负责4列的乘法累加计算。

修正示例(支持block_size=16)

修改blocked_mmul_avx函数,使用4个累加寄存器分别处理16列中的每4列:

void blocked_mmul_avx(double *A, double *B, double *C, int n) {
  register int row, block, chunk, sub_chunk;
  int block_size = 16; // 现在支持16的块大小
  int vec_width = 4; // __m256d一次处理4个double

  for (row = 0; row < n; row++)
    for (block = 0; block < n; block += block_size) {
      // 初始化4个累加寄存器,对应16列中的4组
      __m256d c0 = _mm256_setzero_pd();
      __m256d c1 = _mm256_setzero_pd();
      __m256d c2 = _mm256_setzero_pd();
      __m256d c3 = _mm256_setzero_pd();

      for (chunk = 0; chunk < n; chunk += block_size) {
        for (sub_chunk = 0; sub_chunk < block_size; sub_chunk++) {
          __m256d a = _mm256_broadcast_sd(&A[row * n + chunk + sub_chunk]);
          // 加载B中对应的4组4列数据
          __m256d b0 = _mm256_loadu_pd(&B[(chunk + sub_chunk)*n + block]);
          __m256d b1 = _mm256_loadu_pd(&B[(chunk + sub_chunk)*n + block + vec_width]);
          __m256d b2 = _mm256_loadu_pd(&B[(chunk + sub_chunk)*n + block + vec_width*2]);
          __m256d b3 = _mm256_loadu_pd(&B[(chunk + sub_chunk)*n + block + vec_width*3]);
          // 分别累加
          c0 += a * b0;
          c1 += a * b1;
          c2 += a * b2;
          c3 += a * b3;
        }
      }
      // 将4个寄存器的结果分别存储到C的对应位置
      _mm256_storeu_pd(&C[row*n + block], c0);
      _mm256_storeu_pd(&C[row*n + block + vec_width], c1);
      _mm256_storeu_pd(&C[row*n + block + vec_width*2], c2);
      _mm256_storeu_pd(&C[row*n + block + vec_width*3], c3);
    }
}

内容的提问来源于stack exchange,提问作者Mohammad

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 07:40:23