- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
问题
我正在研究高性能矩阵乘法算法,例如 OpenBLAS 或 GotoBLAS,我正在尝试重现一些结果。这个问题涉及矩阵乘法算法的内部内核。具体来说,我正在研究计算 C += AB
,其中 A
和 B
是 double
类型的 2x2 矩阵以我的 CPU 的峰值速度。有两种方法可以做到这一点。一种方法是使用 SIMD 指令。第二种方法是使用 SIMD 寄存器直接在汇编中编码。
到目前为止我看过什么
所有相关论文、类(class)网页、许多关于该主题的 SO Q&A(太多无法一一列举),我在我的电脑上编译了 OpenBLAS,浏览了 OpenBLAS、GotoBLAS 和 BLIS 源代码,Agner 的手册。
硬件
我的 CPU 是 Intel i5 - 540M。您可以在 cpu-world.com 上找到相关的 CPUID 信息。微架构是 Nehalem (westmere),因此理论上每个内核每个周期可以计算 4 个 double 触发器。我将只使用一个内核(不使用 OpenMP),因此在关闭超线程和 4 步 Intel Turbo Boost 后,我应该会看到峰值 ( 2.533 Ghz + 4*0.133 Ghz ) * ( 4 DP flops/core/cycle ) * ( 1 core ) = 12.27 DP Gflops
.作为引用,当两个内核都在峰值运行时,Intel Turbo Boost 提供了 2 步加速,我应该得到 22.4 DP Gflops
的理论峰值。 .
设置
我将我的 2x2 矩阵声明为 double
并使用随机条目初始化它们,如下面的代码片段所示。
srand(time(NULL));
const int n = 2;
double A[n*n];
double B[n*n];
double C[n*n];
double T[n*n];
for(int i = 0; i < n*n; i++){
A[i] = (double) rand()/RAND_MAX;
B[i] = (double) rand()/RAND_MAX;
C[i] = 0.0;
}
// "true" answer
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
for(int k = 0; k < n; k++)
T[i*n + j] += A[i*n + k]*B[k*n + j];
for
中执行它。循环
maxiter
次,确保将
C
归零每次计算时的矩阵
C += AB
.
for
循环放置在两个
clock()
内语句,这用于估计 Gflops。代码片段打击说明了这部分。
C[0] = 0.0; C[1] = 0.0; C[2] = 0.0; C[3] = 0.0;
mult2by2(A,B,C); //warmup
time1 = clock();
for(int i = 0; i < maxiter; i++){
mult2by2(A,B,C);
C[0] = 0.0; C[1] = 0.0; C[2] = 0.0; C[3] = 0.0;
}
time2 = clock() - time1;
time3 = (double)(time2)/CLOCKS_PER_SEC;
gflops = (double) (2.0*n*n*n)/time3/1.0e9*maxiter;
mult2by2(A,B,C); // to compute the norm against T
norm = L2norm(n,C,T);
double
每个 vector 中的 s。这是我在内核中进行 2x2 矩阵乘法的主要原因。 SIMD 代码计算一整行
C
一次。
inline void
__attribute__ ((gnu_inline))
__attribute__ ((aligned(16))) mult2by2B(
const double* restrict A,
const double* restrict B,
double* restrict C
)
{
register __m128d xmm0, xmm1, xmm2, xmm3, xmm4;
xmm0 = _mm_load_pd(C);
xmm1 = _mm_load1_pd(A);
xmm2 = _mm_load_pd(B);
xmm3 = _mm_load1_pd(A + 1);
xmm4 = _mm_load_pd(B + 2);
xmm1 = _mm_mul_pd(xmm1,xmm2);
xmm2 = _mm_add_pd(xmm1,xmm0);
xmm1 = _mm_mul_pd(xmm3,xmm4);
xmm2 = _mm_add_pd(xmm1,xmm2);
_mm_store_pd(C,xmm2);
xmm0 = _mm_load_pd(C + 2);
xmm1 = _mm_load1_pd(A + 2);
xmm2 = _mm_load_pd(B);
xmm3 = _mm_load1_pd(A + 3);
//xmm4 = _mm_load_pd(B + 2);
xmm1 = _mm_mul_pd(xmm1,xmm2);
xmm2 = _mm_add_pd(xmm1,xmm0);
xmm1 = _mm_mul_pd(xmm3,xmm4);
xmm2 = _mm_add_pd(xmm1,xmm2);
_mm_store_pd(C + 2,xmm2);
}
main
中调用它。常规。但是,它非常慢,因为我无法内联
extern
职能。我将程序集编写为内联程序集,如下所示。它与
gcc -S -std=c99 -O3 -msse3 -ffast-math -march=nocona -mtune=nocona -funroll-all-loops -fomit-frame-pointer -masm=intel
产生的相同.根据我对Nehalem微架构图的了解,这款处理器可以执行
SSE ADD
,
SSE MUL
, 和
SSE MOV
并行,这解释了
MUL
的交错,
ADD
,
MOV
指示。您会注意到上面的 SIMD 指令的顺序不同,因为我对 Agner Fog 的手册有不同的理解。尽管如此,
gcc
很聪明,上面的 SIMD 代码编译为内联版本中显示的程序集。
inline void
__attribute__ ((gnu_inline))
__attribute__ ((aligned(16))) mult2by2A
(
const double* restrict A,
const double* restrict B,
double* restrict C
)
{
__asm__ __volatile__
(
"mov edx, %[A] \n\t"
"mov ecx, %[B] \n\t"
"mov eax, %[C] \n\t"
"movapd xmm3, XMMWORD PTR [ecx] \n\t"
"movapd xmm2, XMMWORD PTR [ecx+16] \n\t"
"movddup xmm1, QWORD PTR [edx] \n\t"
"mulpd xmm1, xmm3 \n\t"
"addpd xmm1, XMMWORD PTR [eax] \n\t"
"movddup xmm0, QWORD PTR [edx+8] \n\t"
"mulpd xmm0, xmm2 \n\t"
"addpd xmm0, xmm1 \n\t"
"movapd XMMWORD PTR [eax], xmm0 \n\t"
"movddup xmm4, QWORD PTR [edx+16] \n\t"
"mulpd xmm4, xmm3 \n\t"
"addpd xmm4, XMMWORD PTR [eax+16] \n\t"
"movddup xmm5, QWORD PTR [edx+24] \n\t"
"mulpd xmm5, xmm2 \n\t"
"addpd xmm5, xmm4 \n\t"
"movapd XMMWORD PTR [eax+16], xmm5 \n\t"
: // no outputs
: // inputs
[A] "m" (A),
[B] "m" (B),
[C] "m" (C)
: //register clobber
"memory",
"edx","ecx","eax",
"xmm0","xmm1","xmm2","xmm3","xmm4","xmm5"
);
}
gcc -std=c99 -O3 -msse3 -ffast-math -march=nocona -mtune=nocona -funroll-all-loops -fomit-frame-pointer -masm=intel
maxiter = 1000000000
的结果如下:
********** Inline ASM
L2 norm: 0.000000e+000, Avg. CPU time: 9.563000, Avg. Gflops: 1.673115
********** SIMD Version
L2 norm: 0.000000e+000, Avg. CPU time: 0.359000, Avg. Gflops: 44.568245
__attribute__ ((noinline))
内联,结果是:
********** Inline ASM
L2 norm: 0.000000e+000, Avg. CPU time: 11.155000, Avg. Gflops: 1.434334
********** SIMD Version
L2 norm: 0.000000e+000, Avg. CPU time: 11.264000, Avg. Gflops: 1.420455
Compiled code may be faster than assembly code because compilers can make inter-procedural optimization and whole-program optimization. The assembly programmer usually has to make well-defined functions with a well-defined call interface that obeys all calling conventions in order to make the code testable and verifiable. This prevents many of the optimization methods that compilers use, such as function inlining, register allocation, constant propagation, common subexpression elimination across functions, scheduling across functions, etc. These advantages can be obtained by using C++ code with intrinsic functions instead of assembly code.
-S
输出显示
for
SIMD 循环仅零
C
矩阵。我可以通过使用
-O0
关闭编译器优化来禁用它.在这种情况下,SIMD 的运行速度是 ASM 的 3 倍,但 ASM 仍然以完全相同的速度运行。范数现在也非零,但在 10^-16 处仍然可以。我还看到内联 ASM 版本正在与
APP
内联。和
NO_APP
标签,但它也在
for
中展开了 8 次环形。我认为多次展开会严重影响性能,因为我通常会展开循环 4 次。根据我的经验,更多的东西似乎会降低性能。
最佳答案
GCC 正在使用内在函数优化您的内联函数,mult2by2B
,由于线
C[0] = 0.0; C[1] = 0.0; C[2] = 0.0; C[3] = 0.0;
mult2by2A
, away (即使它内联它)。您也可以在程序集中看到这一点。
#include <stdio.h>
#include <emmintrin.h> // SSE2
#include <omp.h>
inline void
__attribute__ ((gnu_inline))
__attribute__ ((aligned(16))) mult2by2B(
const double* __restrict A,
const double* __restrict B,
double* __restrict C
)
{
register __m128d xmm0, xmm1, xmm2, xmm3, xmm4;
xmm0 = _mm_load_pd(C);
xmm1 = _mm_load1_pd(A);
xmm2 = _mm_load_pd(B);
xmm3 = _mm_load1_pd(A + 1);
xmm4 = _mm_load_pd(B + 2);
xmm1 = _mm_mul_pd(xmm1,xmm2);
xmm2 = _mm_add_pd(xmm1,xmm0);
xmm1 = _mm_mul_pd(xmm3,xmm4);
xmm2 = _mm_add_pd(xmm1,xmm2);
_mm_store_pd(C,xmm2);
xmm0 = _mm_load_pd(C + 2);
xmm1 = _mm_load1_pd(A + 2);
xmm2 = _mm_load_pd(B);
xmm3 = _mm_load1_pd(A + 3);
//xmm4 = _mm_load_pd(B + 2);
xmm1 = _mm_mul_pd(xmm1,xmm2);
xmm2 = _mm_add_pd(xmm1,xmm0);
xmm1 = _mm_mul_pd(xmm3,xmm4);
xmm2 = _mm_add_pd(xmm1,xmm2);
_mm_store_pd(C + 2,xmm2);
}
int main() {
double A[4], B[4], C[4];
int maxiter = 10000000;
//int maxiter = 1000000000;
double dtime;
dtime = omp_get_wtime();
for(int i = 0; i < maxiter; i++){
mult2by2B(A,B,C);
C[0] = 0.0; C[1] = 0.0; C[2] = 0.0; C[3] = 0.0;
}
dtime = omp_get_wtime() - dtime;
printf("%f %f %f %f\n", C[0], C[1], C[2], C[3]);
//gflops = (double) (2.0*n*n*n)/time3/1.0e9*maxiter;
printf("time %f\n", dtime);
}
关于c - 优化的 2x2 矩阵乘法 : Slow assembly versus fast SIMD,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23790842/
假设我有两个矩阵,每个矩阵有两列和不同的行数。我想检查并查看一个矩阵的哪些对在另一个矩阵中。如果这些是一维的,我通常只会做 a %in% x得到我的结果。 match似乎只适用于向量。 > a
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 9 个月前。 Improv
我只处理过 DirectX 矩阵 我读过一些文章,说不能将 DirectX 矩阵数学库用于 openGL 矩阵。 但我也读过,如果你的数学是一致的,你可以获得类似的结果。那只会让我更加困惑。 任何人都
我编写了一个C++代码来解决线性系统A.x = b,其中A是一个对称矩阵,方法是首先使用LAPACK(E)对角矩阵A = V.D.V^T(因为以后需要特征值),然后求解x = A^-1.b = V^T
我遇到了问题。我想创建二维数组 rows=3 cols=2我的代码如下 int **ptr; int row=3; int col=2; ptr=new int *[col]; for (int i=
我有一个 3d mxnxt 矩阵,我希望能够提取 t 2d nxm 矩阵。在我的例子中,我有一个 1024x1024x10 矩阵,我想要 10 张图像显示给我。 这不是 reshape ,我每次只需要
我在 MATLAB 中有一个 3d 矩阵 (n-by-m-by-t) 表示一段时间内网格中的 n-by-m 测量值.我想要一个二维矩阵,其中空间信息消失了,只剩下 n*m 随着时间 t 的测量值(即:
作为一个简化的示例,我有一个 3D numpy 矩阵,如下所示: a = np.array([[[1,2], [4,np.nan], [7,
作为一个简化的示例,我有一个 3D numpy 矩阵,如下所示: a = np.array([[[1,2], [4,np.nan], [7,
使用 eigen2 , 并给定一个矩阵 A a_0_0, a_0_1, a_0_2, ... a_1_0, a_1_0, a_1_2, ... ... 和一个矩阵B: b_0_0, b_0_1, b_
我想知道如何获得下面的布局。 在中型和大型设备上,我希望有 2 行和 2 列的布局(2 x 2 矩阵)。 在小型(和超小型)设备上或调整为小型设备时,我想要一个 4 行和 1 列的矩阵。 我将通过 a
有什么方法可以向量化以下内容: for i = 1:6 te = k(:,:,:,i).*(c(i)); end 我正在尝试将 4D 矩阵 k 乘以向量 c,方法是将其
如何从填充有 1 和 0 的矩阵中抽取 n 个随机点的样本? a=rep(0:1,5) b=rep(0,10) c=rep(1,10) dataset=matrix(cbind(a,b,c),nrow
我正在尝试创建一个包含 X 个 X 的矩阵。以下代码生成从左上角到右下角的 X 对 Angular 线,而不是从右上角到左下角的 X 对 Angular 线。我不确定从哪里开始。是否应该使用新变量创建
我想在 python 中创建一个每行三列的矩阵,并能够通过任何一行对它们进行索引。矩阵中的每个值都是唯一的。 据我所知,我可以设置如下矩阵: matrix = [["username", "name"
我有点迷茫 我创建了一个名为 person 的类,它具有 age 和 name 属性(以及 get set 方法)。然后在另一个类中,我想创建一个 persons 数组,其中每个人都有不同的年龄和姓名
我有 n 个类,它们要么堆叠,要么不堆叠。所有这些类都扩展了同一个类 (CellObject)。我知道更多类将添加到此列表中,我想创建一种易于在一个地方操纵“可堆叠性”的方法。 我正在考虑创建一个矩阵
我有一个包含 x 个字符串名称及其关联 ID 的文件。本质上是两列数据。 我想要的是一个格式为 x x x 的相关样式表(将相关数据同时作为 x 轴和 y 轴),但我想要 fuzzywuzzy 库的函
机器学习与传统编程的一个重要区别在于机器学习比传统编程涉及了更多的数学知识。不过,随着机器学习的飞速发展,各种框架应运而生,在数据分析等应用中使用机器学习时,使用现成的库和框架成为常态,似乎越来越不需
当我在 julia 中输入这个错误跳转但我不知道为什么,它应该工作。/ julia> A = [1 2 3 4; 5 6 7 8; 1 2 3 4; 5 6 7 8] 4×4 Array{Int64,
我是一名优秀的程序员,十分优秀!