这个问题是对“ HPC的C ++ vs Fortran ”的答复中最近进行的两个讨论的扩展。而且,这不仅仅是一个挑战,而不是一个问题...
支持Fortran的最常听到的论据之一是编译器更好。由于大多数C / Fortran编译器共享相同的后端,因此为两种语言在语义上等效的程序生成的代码应该相同。但是,有人可能会争辩说,对于编译器来说,C / Fortran更容易实现优化。
因此,我决定尝试一个简单的测试:我获得了daxpy.f和daxpy.c的副本,并使用gfortran / gcc对其进行了编译。
现在daxpy.c只是daxpy.f(自动生成的代码,很难看的丑陋的)的f2c转换,所以我拿走了这段代码并对其进行了一些清理(满足daxpy_c),这基本上意味着将最里面的循环重写为
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
最后,我使用gcc的向量语法重新编写了它(输入daxpy_cvec):
#define vector(elcount, type) __attribute__((vector_size((elcount)*sizeof(type)))) type
vector(2,double) va = { da , da }, *vx, *vy;
vx = (void *)dx; vy = (void *)dy;
for ( i = 0 ; i < (n/2 & ~1) ; i += 2 ) {
vy[i] += va * vx[i];
vy[i+1] += va * vx[i+1];
}
for ( i = n & ~3 ; i < n ; i++ )
dy[i] += da * dx[i];
请注意,我使用长度为2的向量(这是SSE2所允许的),并且我一次处理两个向量。这是因为在许多体系结构中,乘法单元可能比矢量元素更多。
所有代码均使用gfortran / gcc 4.5版进行了编译,其标志为“ -O3 -Wall -msse2 -march = native -ffast-math -fomit-frame-pointer -malign-double -fstrict-aliasing”。在我的笔记本电脑(Intel Core i5 CPU,M560、2.67GHz)上,我得到以下输出:
pedro@laika:~/work/fvsc$ ./test 1000000 10000
timing 1000000 runs with a vector of length 10000.
daxpy_f took 8156.7 ms.
daxpy_f2c took 10568.1 ms.
daxpy_c took 7912.8 ms.
daxpy_cvec took 5670.8 ms.
因此,原始的Fortran代码花费了超过8.1秒的时间,其自动翻译花费了10.5秒,朴素的C实现在7.9中完成了,而显式矢量化的代码在5.6中完成了,少了一点。
这比原始的C实现要慢一些,而Fortran的矢量实现要慢50%。
因此,这里的问题是:我是本机C程序员,因此我很自信自己在该代码上做得很好,但是Fortran代码最后一次接触是在1993年,因此可能有点过时了。由于我不太喜欢使用Fortran进行编码,因此有人可以做得更好,即与这两个C版本相比,是否更具竞争力?
另外,有人可以通过icc / ifort尝试此测试吗?向量语法可能不起作用,但是我很想知道天真的C版本在这里的表现。xlc / xlf随处可见的任何人也一样。
我已经在这里上传了源代码和一个Makefile 。为了获得准确的时序,请将test.c中的CPU_TPS设置为CPU上的Hz数。如果您发现任何版本的改进,请在此处发布!
更新:
我已经将stali的测试代码添加到在线文件中,并用C版本进行了补充。我修改程序以对长度为10'000的向量进行1'000'000循环以与之前的测试一致(并且因为我的机器无法分配长度为1'000'000'000的向量,如stali的原始版本一样)码)。由于现在的数字要小一些,因此我使用该选项-par-threshold:50
使编译器更可能并行化。使用的icc / ifort版本为12.1.2 20111128,结果如下
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_c
3.27user 0.00system 0:03.27elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./icctest_f
3.29user 0.00system 0:03.29elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_c
4.89user 0.00system 0:02.60elapsed 188%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./icctest_f
4.91user 0.00system 0:02.60elapsed 188%CPU
总之,对于所有实际目的,结果对于C和Fortran版本都是相同的,并且两个代码都自动进行了并行化。请注意,与之前的测试相比,更快的时间是由于使用了单精度浮点算法!
更新:
尽管我真的不喜欢证明责任在这里,但我还是用C 重新编码了stali的矩阵乘法示例,并将其添加到了网络上的文件中。这是一个和两个CPU的三重循环的结果:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
triple do time 3.46421700000000
3.63user 0.06system 0:03.70elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_c 2500
triple do time 3.431997791385768
3.58user 0.10system 0:03.69elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
triple do time 5.09631900000000
5.26user 0.06system 0:02.81elapsed 189%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_c 2500
triple do time 2.298916975280899
4.78user 0.08system 0:02.62elapsed 184%CPU
请注意,cpu_time
在Fortran中,它测量的是CPU时间而不是挂钟时间,因此我将这些调用包装起来time
以比较2个CPU 的调用。结果之间没有真正的区别,只是C版本在两个内核上做得更好。
现在对于matmul
命令,当然仅在Fortran中,因为此内在函数在C中不可用:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 2500
matmul time 23.6494780000000
23.80user 0.08system 0:23.91elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 2500
matmul time 26.6176640000000
26.75user 0.10system 0:13.62elapsed 197%CPU
哇。太可怕了 谁能找出我在做什么错,还是可以解释为什么这种内在因素仍然是件好事?
我没有将dgemm
调用添加到基准测试中,因为它们是对英特尔MKL中相同功能的库调用。
在以后的测试中,谁能提出一个已知的 C语言比Fortran慢的示例?
更新资料
为了验证stali的说法,即matmul
在较小的矩阵上本征比显式矩阵乘积要快“数量级”,我修改了自己的代码,使用这两种方法将大小为100x100的矩阵相乘,每种方法相乘10 000次。在一个和两个CPU上的结果如下:
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=1 time ./mm_test_f 10000 100
matmul time 3.61222500000000
triple do time 3.54022200000000
7.15user 0.00system 0:07.16elapsed 99%CPU
pedro@laika:~/work/fvsc$ OMP_NUM_THREADS=2 time ./mm_test_f 10000 100
matmul time 4.54428400000000
triple do time 4.31626900000000
8.86user 0.00system 0:04.60elapsed 192%CPU
更新资料
Grisu指出了正确的一点,即不进行优化,gcc会将对复数的运算转换为库函数调用,而gfortran则在几条指令中内联它们。
如果-fcx-limited-range
设置了该选项,则C编译器将生成相同的紧凑代码,即,指示编译器忽略中间值中潜在的上溢/下溢。默认情况下,该选项在gfortran中已设置,可能会导致错误的结果。强制-fno-cx-limited-range
gfortran并没有改变任何东西。
因此,这实际上是反对使用gfortran进行数值计算的一个论点:即使正确的结果在浮点范围内,对复杂值的运算也可能溢出/溢出。这实际上是Fortran标准。在gcc或一般在C99中,除非另有说明,否则默认设置为严格执行操作(阅读IEEE-754兼容标准)。
提醒:请记住,主要问题是Fortran编译器是否产生比C编译器更好的代码。这里不是讨论一种语言胜过另一种语言的一般优点的地方。我真正感兴趣的是,如果有人可以找到一种方法,使用显式矢量化来哄骗gfortran来产生与C语言一样高效的daxpy,因为这例证了必须依靠编译器进行SIMD优化的问题,或者Fortran编译器胜过其C副本的情况。