GCC 向量扩展的内存对齐问题
Posted
技术标签:
【中文标题】GCC 向量扩展的内存对齐问题【英文标题】:Memory Alignment Issues with GCC Vector Extension 【发布时间】:2017-01-20 09:17:17 【问题描述】:我正在尝试使用 GCC 向量扩展 (https://gcc.gnu.org/onlinedocs/gcc/Vector-Extensions.html) 来加速矩阵乘法。这个想法是使用 SIMD 指令一次将四个浮点数相乘和相加。下面列出了一个最小的工作示例。该示例在将 (M=10,K=12) 矩阵乘以 (K=12,N=12) 矩阵时工作正常。但是,当我更改参数(例如 N=9)时,出现分段错误。
我怀疑这是由于内存对齐问题。据我了解,当对 16 字节的向量(在本例中为 float4)使用 SIMD 时,目标内存地址应该是 16 的倍数。已经讨论过 SIMD 指令的内存对齐问题。 (例如Relationship between SSE vectorization and Memory alignment)。在下面的示例中,当 &b(0,0) 为 0x810e10 时,&b(1,0) 为 0x810e34,它不是 16 的倍数。
我的问题是,
-
我确实遇到了内存对齐问题的段错误吗?
谁能告诉我如何轻松解决问题?我曾想过使用二维数组而不是一个数组,但我不想这样做,以免更改其余代码。
最小的工作示例
#include <iostream>
#include <cstdlib>
#include <stdio.h>
#include <cstring>
#include <assert.h>
#include <algorithm>
using namespace std;
typedef float float4 __attribute__((vector_size (16)));
static inline void * alloc64(size_t sz)
void * a = 0;
if (posix_memalign(&a, 64, sz) != 0)
perror("posix_memalign");
exit(1);
return a;
struct Mat
size_t m,n;
float * a;
Mat(size_t m_, size_t n_, float f)
m = m_;
n = n_;
a = (float*) malloc(sizeof(float) * m * n);
fill(a,a + m * n,f);
/* a(i,j) */
float& operator()(long i, long j)
return a[i * n + j];
;
Mat operator* (Mat a, Mat b)
Mat c(a.m, b.n,0);
assert(a.n == b.m);
for (long i = 0; i < a.m; i++)
for(long k = 0; k < a.n; k++)
float aa = a(i,k);
float4 a4 = aa,aa,aa,aa;
long j;
for (j = 0; j <= b.n-4; j+=4)
*((float4 *)&c(i,j)) = *((float4 *)&c(i,j)) + a4 * (*(float4 *)&b(k,j));
while(j < b.n)
c(i,j) += aa * b(k,j);
j++;
return c;
const int M = 10;
const int K = 12;
const int N = 12;
int main()
Mat a(M,K,1);
Mat b(K,N,1);
Mat c = a * b;
for(int i = 0; i < M; i++)
for(int j = 0; j < N; j++)
cout << c(i,j) << " ";
cout << endl;
cout << endl;
【问题讨论】:
【参考方案1】:据我了解,当对 16 字节的向量使用 SIMD 时(在 这种情况下 float4),目标内存地址应该是的倍数 16.
这在 x64 处理器上是不正确的。有些指令需要对齐,但您可以使用正确的指令从未对齐的内存位置很好地写入和读取 SIMD 寄存器,而不会受到惩罚并且绝对安全。
我确实得到了内存对齐的段错误 问题?
是的。
但它与 SIMD 指令无关。在 C/C++ 中,以您的方式编写 *((float4 *)&c) = ...
是未定义的行为,并且肯定会崩溃,但是您可以在不进行矢量化的情况下重现问题...在适当的情况下,以下基本代码将崩溃...
char * c = ...
*(int *) c = 1;
谁能告诉我如何轻松解决这个问题?我想过 使用二维数组而不是一个数组,但我不想要 这样做是为了不改变其余的代码。
典型的解决方法是使用memcpy
。让我们看一个代码示例...
#include <string.h>
typedef float float4 __attribute__((vector_size (16)));
void writeover(float * x, float4 y)
*(float4 * ) x = y;
void writeover2(float * x, float4 y)
memcpy(x,&y,sizeof(y));
例如,使用 clang++,这两个函数被编译为 vmovaps
和 vmovups
。这些是等效的指令,但如果您的指针未在 sizeof(float4)
上对齐,第一个指令将崩溃。它们是最新硬件上非常快速的功能。
关键是您通常可以依靠memcpy
生成几乎最佳速度的代码。当然,您获得的开销(如果有的话)将取决于您使用的编译器。
如果您确实遇到了性能问题,那么您可以改用 Intel 内部函数或程序集...但很有可能memcpy
可以很好地为您服务。
一个不同的解决方法是仅在 float4 *
指针方面工作。这会强制您的所有矩阵具有可被 4 整除的维度,但是如果您用零填充剩余部分,您可能会得到简单且非常快速的代码。
【讨论】:
非常感谢!我现在太忙了,无法根据您的建议修复我的代码,但我稍后会写一篇后续文章。以上是关于GCC 向量扩展的内存对齐问题的主要内容,如果未能解决你的问题,请参考以下文章