Fortran编译器是否真的比C编译器生成更快的代码?


17

当我在大学学习时,我经常听到这样的想法:对于同等程序,Fortran编译器生成的代码比C编译器更快。

关键原因是这样的:Fortran编译器每行代码平均发出1,1个处理器指令,而C编译器每行代码平均发出1,6处理器指令 -我不记得确切的数字了,但是想法是C编译器发出明显更多的机器代码,因此产生较慢的程序。

这样的比较有效吗?我们可以说Fortran编译器比C编译器生成更快的程序,反之亦然,为什么存在这种差异?


19
这可能仅仅意味着Fortran程序比C语言更冗长。只有通过两种语言实现相同的功能并比较最终的机器代码(大小和速度),才能进行有意义的比较。
彼得Török

另外,生成的代码是否支持并行执行?

@PéterTörök,这只是意味着,例如,Fortran中的BLAS和LAPACK的性能要比其任何C / C ++端口都要好。现在,差距正在迅速缩小。
SK-logic

6
如果您拥有两种语言的100%等效程序,并且由了解他们的编译器并且可以考虑性能的专家编写,则只能说一个编译器会产生更快的代码。
猎鹰

以前的Fortran不支持递归,因此不必将函数调用参数推入堆栈,因为每个函数的参数都有一个静态分配的空间。这是它可能更快的原因之一。我想你可以在这里找到一个更完整的答案:amazon.com/Programming-Language-Pragmatics-Third-Edition/dp/...
佩德罗·罗洛

Answers:


36

IIRC之所以说Fortran更快的主要原因之一是缺少指针别名,因此它们可以使用C编译器不能使用的优化:

在FORTRAN中,函数参数可能不会互为别名,并且编译器会假定它们没有别名。这可以实现出色的优化,这也是FORTRAN以快速语言而闻名的主要原因之一。(请注意,在FORTRAN函数中仍可能发生别名。例如,如果A是一个数组,而i和j是碰巧具有相同值的索引,则A [i]和A [j]是两个不同的名称幸运的是,由于基本数组必须具有相同的名称,因此可以进行索引分析以确定A [i]和A [j]不能别名的情况。

但我在这里与其他人相同:比较为一行代码生成的汇编程序指令的平均数目是完全无稽之谈。例如,现代x86内核如果不访问相同的寄存器,则可以并行执行两条指令。因此,从理论上讲,只需对指令重新排序,就可以使相同指令集的性能提高100%。好的编译器通常还会生成更多的汇编指令以获取更快的代码(请考虑展开循环,内联)。汇编指令的总数很少说明一段代码的性能。


更好的优化的另一个原因是对复数的本地支持。
SK-logic

对于Fortran IV来说肯定是正确的。不知道现代的FORTRAN是否仍然没有指针,动态内存等。–
Ingo

2
这就是我们在游戏行业使用C和C ++开发时经常掉入一些内联汇编的原因。人们可以随心所欲地宣称“编译器比编写汇编程序的人可以优化的更好”,事实是,指针混叠意味着他们常常不能。我们可以手工编写的代码在技术上对于编译器发出是非法的,因为它对指针别名没有任何作用。
Carson63000

5
C的restrict关键字允许函数的作者指定指针没有别名。这是否足以解决差异,还是还有更多呢?
bk。

@bk .: C的“限制”攻击“使问题减半”;它可以说一个特定的指针在其生命周期内不会别名,但无法告诉编译器,一旦函数返回,其地址已传递给函数的对象将不会被别名。
超级猫

8

完全无效的比较。

首先,正如@PéterTörök指出的那样,您必须首先比较来自Fortran和C的等效程序中的行数,才能对产生的行数进行有效比较。

其次,更少的代码行并不总是等于更快的程序。并非所有的机器指令都需要执行相同数量的循环,但是您还遇到其他问题,例如内存访问缓存等。

最重要的是,较长的代码运行速度会更快,因为这会导致执行行数减少(即Line Count!= Executed Line Count)。


5

Dan是正确的,较长的程序并不意味着较慢的程序。这很大程度上取决于他们在做什么。

我不是Fortran的专家,我知道一点。比较它们,我认为写得很好的C在性能上比Fortran更好,并且具有更复杂的数据结构和功能。如果我在这里错了,请有人(请)纠正我,但我确实认为Fortran的水平要比C低。

乍一看,我想你是在问编译器是否更快。实际上,我确实认为,Fortran在使用相似数量的代码时通常会编译得更快,但是生成的程序及其运行方式将是另一回事。解析起来更简单。


2
如果使用复杂的数据结构,则FORTRAN可能是错误的选择。FORTRAN经过优化,可以非常快速地进行简单的数字运算。
Zachary K

4

我认为部分原因是FORTRAN编译器旨在非常快速地执行某些类型的数学运算。这就是为什么人们使用FORTRAN来尽可能快地进行计算的原因


4

该声明可能在C处于起步阶段的过去(大约70年代后期)是正确的,并且Fortran得到了所有主要制造商的支持并进行了高度优化。早期的Fortrans基于IBM体系结构,因此诸如算术之类的简单东西肯定会是每个汇编指令一个语句。对于像Data General和Prime这样的较旧机器来说,这是正确的,它们具有3种跳跃方式。这不适用于没有三向跳转的现代指令集。

代码行不等于代码声明。早期版本的Fortran仅允许每行一个语句。更高版本的Fortran可以每行使用多个语句。C每行可以有多个语句。在像英特尔的IVF(以前称为CVF,MS Powerstation)和英特尔的C之类的速度更快的生产编译器上,两者之间确实没有区别。这些编译器经过高度优化。


4

旧式FORTRAN要求程序员想要将数组的一部分提供给函数,以便将引用传递给整个数组,以及一个或多个整数值,这些整数值指定开始下标和结束下标或项数。C可以简化此过程,只需将指针传递到与元素数量一起感兴趣部分。直接来说,这会使事情变得更快(通过两件事而不是三件事)。但是,通过限制编译器可以执行的优化类型,它可能间接地减慢了运行速度。

考虑以下功能:

void diff(float dest[], float src1[], float src2[], int n)
{
  for (int i=0; i<n; i++)
    dest[i] = src1[i] - src2[i];
}

如果编译器知道每个指针都将标识数组的开始,则它可以生成将并行或以任何顺序作用于数组元素的代码,因为对于任何x!= y,对dest [x ]不会影响src1 [y]或src2 [y]。例如,在某些系统上,编译器可能会受益于生成等效于以下代码的代码:

void dif(float dest[], float src1[], float src2[], int n)
{
  int i=0;
  float t1a,t1b,t2a,t2b,tsa,tsb;
  if (n > 2)
  {
    n-=4;
    t1a = src1[n+3]; t1b = src2[n+3]; t1b=src2[n+2]; t2b = src2[n+2];
    do
    {
      tsa = t1a-t2a;
      t1a = src1[n+1]; t2a = src2[n+1]; 
      tsb = t2b-t2b;
      dest[n+3] = tsa;
      t1b = src1[n]; t2b = src2[n]; 
      n-=2;
      dest[n+4] = tsb;
    } while(n >= 0);
    ... add some extra code to handle cleanup
  }
  else
    ... add some extra code to handle small values of n
}

请注意,每个加载或计算值的操作在它与使用该值的下一个操作之间至少还有一个操作。当满足这样的条件时,某些处理器可能会使不同操作的处理重叠,从而提高性能。但是请注意,由于C编译器无法知道不会将代码传递给指向公共数组的部分重叠区域的指针,因此C编译器无法进行上述转换。但是,给定等效代码的FORTRAN编译器可以并且确实进行了这种转换。

尽管C程序员可以尝试通过显式编写出展开循环并使相邻遍历的操作重叠的代码来获得可比的性能,但是如果这样的代码使用了太多的自动变量,则编译器不得不“溢出”它们,从而很容易降低性能。记忆。FORTRAN编译器的优化器可能比程序员更了解在给定情况下哪种形式的交织会产生最佳性能,并且通常最好由此类编译器来决定。尽管C99尝试通过添加restrict限定符来改善C的情况,但只有在与和dest[]分开的数组或程序员添加单独的循环版本来处理所有的情况下,才能在此处使用C99src1[]src2[]dest是不相交的src1src2,其中src1[]并且dest相等src2且不相交,其中src2[]dest[]相等src1且不相交,并且所有三个数组均相等。相比之下,FORTRAN可以使用相同的源代码和相同的机器代码轻松处理所有四种情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.