我在xeon处理器系统上使用C和FORTRAN运行n*n
矩阵乘法代码。我很惊讶地看到两种方法之间的实时差异。为什么FORTRAN代码给了我更快的执行时间?我正在使用dgemm()
并从我的C代码调用相同的函数。我试图运行通用C代码来改变循环顺序并尝试使用不同的标志来优化模拟过程。我无法达到使用dgemm()
获得的相同响应。
FORTRAN代码 - dgemm():
#include "stdio.h"
#include "time.h"
#include "sys/time.h"
#include "math.h"
#include "stdlib.h"
long long readTSC(void)
{
/* read the time stamp counter on Intel x86 chips */
union { long long complete; unsigned int part[2]; } ticks;
__asm__ ("rdtsc; mov %%eax,%0;mov %%edx,%1"
: "=mr" (ticks.part[0]),
"=mr" (ticks.part[1])
: /* no inputs */
: "eax", "edx");
return ticks.complete;
}
volatile double gtod(void)
{
static struct timeval tv;
static struct timezone tz;
gettimeofday(&tv,&tz);
return tv.tv_sec + 1.e-6*tv.tv_usec;
}
void dgemm (char *transa, char *transb, int *x, int *xa, int *xb, double *alphaa, double *ma, int *xc, double *mb, int *xd, double *betaa, double *msum, int *xe);
int main(int argc, char** argv)
{
int n = atoi(argv[1]);
long long tm;
//disabling transpose, disabling addition operation in C := alpha*op(A)*op(B) + beta*C
char trans='N';
double alpha=1.0;
double beta=0.0;
long long int p=2*n*n*n;
long double q;
double *a,*b,*sum;
double t_real,t,flop_clk,flops;
int i,j,k;
//memory allocation
a=(double*)malloc(n*n*sizeof(double));
b=(double*)malloc(n*n*sizeof(double));
sum=(double*)malloc(n*n*sizeof(double));
//Matrix Initialization
for (i=0;i<n;i++)
{
for (j=0;j<n;j++)
{
a[i+n*j]=(double)rand();
b[i+n*j]=(double)rand();
sum[i+n*j]=0.0;
}
}
//Clock cycles computation using timing2 function and t_real using timing1 function
t = gtod();
tm = readTSC();
//dgemm function call
dgemm(&trans, &trans, &n, &n, &n, &alpha, a, &n, b, &n, &beta, sum, &n);
tm = readTSC() - tm;
t_real = gtod() - t;
return 0;
}
C代码 只需取sum = 0然后
for (i=0;i<n;i++)
{
for (k=0;k<n;k++)
{
for (j=0;j<n;j++)
{
sum [i+n*j] +=a[i+n*k]*b[k+n*j];
}
}
}
汇编:
icc -o可执行程序c,用于C代码
icc -o executable program.c mkl = Fortran one的顺序
性能
矩阵顺序为5000 * 5000,我的代码中得到4.2 GFLOPS,使用dgemm()得到21.7 GFLOPS。
答案 0 :(得分:4)
你仍然没有表现出足够明确的答案。值得注意的是,当您说某些内容更快时,如果有关于性能的任何问题,您应该显示您所做的实际测量以及用于编译可执行文件的命令。
无论如何,可以得出一些结论。
您似乎不使用任何优化(-O
或-fast
标记)。任何性能分析都是毫无意义的。
从您展示的源代码中可以清楚地看出,您根本没有比较同一件事,您正在比较两种不同的算法。绝对没有必要比较两种不同算法的速度。 gemm
不包含您在自己的代码中使用的这种简单循环,它更复杂,主要是为了获得最佳缓存利用率。
您可以使用非常简单的方法在您自己的C代码中乘以矩阵。你现在(根据你的一条评论)现在比gemm
更快的事实实际上非常令人担忧。你确定你使用了足够大的矩阵吗?在矩阵10x10上调用gemm
是没有意义的,它们应该有一些实际的大小。对于足够大小的矩阵,gemm
应该比天真循环快得多。如果您不对自己的功能使用任何编译器优化,则4.2和22 GFLOPS的原始数字听起来合理。
您声称自己正在与Fortran进行比较。这不是真的。只有参考BLAS实现是用Fortran编写的,但它并不用于需要快速BLAS的严肃计算。你似乎使用的MKL不是用Fortran编写的,它是一个非常优化的汇编代码。还有其他BLAS实现(ATLAS,GotoBLAS,OpenBLAS),它们通常不是用Fortran编写的,而是用C语言或汇编语言编写的。
答案 1 :(得分:3)
只是一个猜测,因为OP没有显示任何代码。如果他正在调用dgemm(来自LAPACK BLAS),它可能是用Fortran编写的。
Pointer aliasing规则在C和Fortran中有所不同。
在C例程中声明形式时,您可以使用(小心!)restrict
关键字。这应该有所帮助。
此外,算法在C和Fortran中有所不同。在 C的一些方言中(例如C89),每个浮点运算都是在双精度数上计算的。 IIRC在Fortran中的定义不同。它在C89和C之间发生了变化。 C99(也许还有C11)。
如果你的两个代码是由最近的GCC编译的(即使用gcc -O2 foo.c
表示C代码,gfortran -O2 foo.f90
表示Fortran90代码),则两个编译器会产生类似的内部表示(Gimple,您可能会使用-fdump-tree-ssa
或许多其他-fdump
标志来生成数百个转储文件...)然后进行优化。所以在这种情况下编译器后端是相同的,中间端非常相似,但前端真的不同。
您只需查看汇编代码(使用gcc -O2 -fverbose-asm
&amp; gfortran -O2 -fverbose-asm
)并找出差异。
您可以使用其他选项,例如-ffast-math
(使编译器能够针对标准进行优化)或-mtune=native
(要求GCC编译器针对您的特定处理器进行优化)以及{-O2
1}}或-O3
优化标记...