- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我目前正在尝试使用内在函数和循环展开来优化矩阵运算。存在我无法弄清楚的段错误。这是我更改的代码:
const int UNROLL = 4;
void outer_product(matrix *vec1, matrix *vec2, matrix *dst) {
assert(vec1->dim.cols == 1 && vec2->dim.cols == 1 && vec1->dim.rows == dst->dim.rows && vec2->dim.rows == dst->dim.cols);
__m256 tmp[4];
for (int x = 0; x < UNROLL; x++) {
tmp[x] = _mm256_setzero_ps();
}
for (int i = 0; i < vec1->dim.rows; i+=UNROLL*8) {
for (int j = 0; j < vec2->dim.rows; j++) {
__m256 row2 = _mm256_broadcast_ss(&vec2->data[j][0]);
for (int x = 0; x<UNROLL; x++) {
tmp[x] = _mm256_mul_ps(_mm256_load_ps(&vec1->data[i+x*8][0]), row2);
_mm256_store_ps(&dst->data[i+x*8][j], tmp[x]);
}
}
}
}
void matrix_multiply(matrix *mat1, matrix *mat2, matrix *dst) {
assert (mat1->dim.cols == mat2->dim.rows && dst->dim.rows == mat1->dim.rows && dst->dim.cols == mat2->dim.cols);
for (int i = 0; i < mat1->dim.rows; i+=UNROLL*8) {
for (int j = 0; j < mat2->dim.cols; j++) {
__m256 tmp[4];
for (int x = 0; x < UNROLL; x++) {
tmp[x] = _mm256_setzero_ps();
}
for (int k = 0; k < mat1->dim.cols; k++) {
__m256 mat2_s = _mm256_broadcast_ss(&mat2->data[k][j]);
for (int x = 0; x < UNROLL; x++) {
tmp[x] = _mm256_add_ps(tmp[x], _mm256_mul_ps(_mm256_load_ps(&mat1->data[i+x*8][k]), mat2_s));
}
}
for (int x = 0; x < UNROLL; x++) {
_mm256_store_ps(&dst->data[i+x*8][j], tmp[x]);
}
}
}
}
编辑:这是矩阵的结构。我没有修改它。
typedef struct shape {
int rows;
int cols;
} shape;
typedef struct matrix {
shape dim;
float** data;
} matrix;
编辑:我尝试 gdb 找出哪一行导致了段错误,看起来像是 _mm256_load_ps()
。我是否以错误的方式索引到矩阵,以致无法从正确的地址加载?还是内存对齐的问题?
最佳答案
至少在一个地方,您正在执行 32 字节对齐所需的加载,步幅仅为 4 个字节。不过,我认为这不是您真正想要做的:
for (int k = 0; k < mat1->dim.cols; k++) {
for (int x = 0; x < UNROLL; x++) {
...
_mm256_load_ps(&mat1->data[i+x*8][k])
}
}
_mm256_load_ps
加载 8 个连续的 float
s,即加载 data[i+x*8][k]
至data[i+x*8][k+7]
。 我想你想要data[i+x][k*8]
, 并循环 k
在最内层循环中。
如果您需要未对齐的加载/存储,请使用 _mm256_loadu_ps
/_mm256_storeu_ps
。但更喜欢将数据对齐到 32B,并填充矩阵的存储布局,以便行步长是 32 字节的倍数。 (数组的实际逻辑维度不必与步长匹配;可以将每行末尾的填充保留为 16 或 32 字节的倍数。这使得循环更容易编写。)
您甚至没有使用二维数组(您使用的是指向 float
数组的指针数组),但语法看起来与 float A[100][100]
相同。 ,尽管 asm 中的含义非常不同。无论如何,在 Fortran 2D 数组中,索引采用相反的方式,递增最左边的索引会将您带到内存中的下一个位置。但在 C 语言中,将左侧索引改变 1 会将您带到一个全新的行。 (由 float **data
的不同元素指向,或者在适当的二维数组中,一行跨过。)当然,由于这种混合与使用 x*8
相结合,您跨了 8 行。 .
说到 asm,这段代码的结果非常糟糕,尤其是使用 gcc,它为每个 vector 重新加载 4 个东西,我认为是因为不确定 vector 存储不会给指针数据别名。将事物分配给局部变量以确保编译器可以将它们提升出循环。 (例如 const float *mat1dat = mat1->data;
。)Clang 做得稍好一些,但源中的访问模式本质上是不好的,并且需要对每个内循环迭代进行指针追踪才能到达新行,因为您循环了 x
而不是k
。我把它放在 Godbolt compiler explorer .
但实际上,您应该首先优化内存布局,然后再尝试手动矢量化。转置其中一个数组可能是值得的,因此您可以在一个矩阵的行和另一个矩阵的列上循环连续内存,同时进行行和列的点积来计算结果的一个元素。或者这可能是值得做的c[Arow,Bcol] += a_value_from_A * b[Arow,Bcol]
在内循环内部而不是预先转置(但这会占用大量内存流量)。但无论您做什么,请确保您不会跨过对内循环中的矩阵之一的非连续访问。
您还需要放弃指针数组并进行手动 2D 索引( data[row * row_stride + col]
,这样您的数据就全部在一个连续的 block 中,而不是单独分配每一行。在您之前先进行此更改花任何时间手动矢量化,似乎最有意义。
gcc 或 clang 与 -O3
应该完成自动矢量化标量 C 的工作,特别是当您使用 -ffast-math
进行编译时。 (完成手动矢量化后,您可以删除 -ffast-math
,但在自动矢量化调整时使用它)。
相关:
另请参阅我对 Poor maths performance in C vs Python/numpy 的评论另一个糟糕的内存布局问题。
how to optimize matrix multiplication (matmul) code to run fast on a single processor core
您可以在查看缓存阻塞之前或之后手动进行矢量化,但是当您这样做时,请参阅 Matrix Multiplication with blocks .
关于c - 矩阵优化 - 使用内在函数和循环展开时出现段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47402422/
C语言sscanf()函数:从字符串中读取指定格式的数据 头文件: ?
最近,我有一个关于工作预评估的问题,即使查询了每个功能的工作原理,我也不知道如何解决。这是一个伪代码。 下面是一个名为foo()的函数,该函数将被传递一个值并返回一个值。如果将以下值传递给foo函数,
CStr 函数 返回表达式,该表达式已被转换为 String 子类型的 Variant。 CStr(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CSng 函数 返回表达式,该表达式已被转换为 Single 子类型的 Variant。 CSng(expression) expression 参数是任意有效的表达式。 说明 通常,可
CreateObject 函数 创建并返回对 Automation 对象的引用。 CreateObject(servername.typename [, location]) 参数 serv
Cos 函数 返回某个角的余弦值。 Cos(number) number 参数可以是任何将某个角表示为弧度的有效数值表达式。 说明 Cos 函数取某个角并返回直角三角形两边的比值。此比值是
CLng 函数 返回表达式,此表达式已被转换为 Long 子类型的 Variant。 CLng(expression) expression 参数是任意有效的表达式。 说明 通常,您可以使
CInt 函数 返回表达式,此表达式已被转换为 Integer 子类型的 Variant。 CInt(expression) expression 参数是任意有效的表达式。 说明 通常,可
Chr 函数 返回与指定的 ANSI 字符代码相对应的字符。 Chr(charcode) charcode 参数是可以标识字符的数字。 说明 从 0 到 31 的数字表示标准的不可打印的
CDbl 函数 返回表达式,此表达式已被转换为 Double 子类型的 Variant。 CDbl(expression) expression 参数是任意有效的表达式。 说明 通常,您可
CDate 函数 返回表达式,此表达式已被转换为 Date 子类型的 Variant。 CDate(date) date 参数是任意有效的日期表达式。 说明 IsDate 函数用于判断 d
CCur 函数 返回表达式,此表达式已被转换为 Currency 子类型的 Variant。 CCur(expression) expression 参数是任意有效的表达式。 说明 通常,
CByte 函数 返回表达式,此表达式已被转换为 Byte 子类型的 Variant。 CByte(expression) expression 参数是任意有效的表达式。 说明 通常,可以
CBool 函数 返回表达式,此表达式已转换为 Boolean 子类型的 Variant。 CBool(expression) expression 是任意有效的表达式。 说明 如果 ex
Atn 函数 返回数值的反正切值。 Atn(number) number 参数可以是任意有效的数值表达式。 说明 Atn 函数计算直角三角形两个边的比值 (number) 并返回对应角的弧
Asc 函数 返回与字符串的第一个字母对应的 ANSI 字符代码。 Asc(string) string 参数是任意有效的字符串表达式。如果 string 参数未包含字符,则将发生运行时错误。
Array 函数 返回包含数组的 Variant。 Array(arglist) arglist 参数是赋给包含在 Variant 中的数组元素的值的列表(用逗号分隔)。如果没有指定此参数,则
Abs 函数 返回数字的绝对值。 Abs(number) number 参数可以是任意有效的数值表达式。如果 number 包含 Null,则返回 Null;如果是未初始化变量,则返回 0。
FormatPercent 函数 返回表达式,此表达式已被格式化为尾随有 % 符号的百分比(乘以 100 )。 FormatPercent(expression[,NumDigitsAfterD
FormatNumber 函数 返回表达式,此表达式已被格式化为数值。 FormatNumber( expression [,NumDigitsAfterDecimal [,Inc
我是一名优秀的程序员,十分优秀!