Действительно ли компиляторы Fortran генерируют более быстрый код, чем компиляторы C?

17

Когда я учился в университете, я часто слышал мысль о том, что компиляторы Фортрана производят более быстрый код, чем компиляторы Си для эквивалентной программы.

Основные рассуждения звучали так: компилятор Фортрана выдает в среднем по 1,1 инструкции процессора на строку кода, в то время как компилятор C выдает в среднем по 1,6 инструкции процессора на строку кода - я не помню точные цифры, но Идея заключалась в том, что компиляторы C генерировали заметно больше машинного кода и, следовательно, создавали более медленные программы.

Насколько обосновано такое сравнение? Можно ли сказать, что компиляторы Fortran производят более быстрые программы, чем компиляторы C или наоборот, и почему существует эта разница?

Sharptooth
источник
19
Это может просто означать, что программы на Фортране более многословны, чем C ... Значимое сравнение может быть выполнено только путем реализации одинаковых функций на обоих языках и сравнения полученного машинного кода (размера и скорости).
Петер Тёрёк
Кроме того, сгенерированный код поддерживает параллельное выполнение?
@ Péter Török, это просто означает, что, скажем, BLAS и LAPACK в Фортране работали намного лучше, чем любой из их портов C / C ++. Теперь разрыв быстро сокращается.
SK-logic
6
Вы можете только утверждать, что один компилятор производит более быстрый код, если у вас есть 100% эквивалентная программа на обоих языках, написанная экспертами, которые знают свои компиляторы и которые могут отчитаться за производительность.
Сокол
Прежний Fortran не поддерживал рекурсию и, следовательно, не обязательно должен был помещать аргументы вызова функции в стек, поскольку для аргументов каждой функции было бы статически выделенное пространство. Это одна из причин, почему это могло бы быть быстрее. Я думаю, вы можете найти более полный ответ здесь: amazon.com/Programming-Language-Pragmatics-Third-Edition/dp/…
Педро Роло

Ответы:

36

Одна из главных причин того, почему Fortran считается более быстрым, - это отсутствие псевдонимов указателей , поэтому они могут использовать оптимизации, которые компиляторы C не могут использовать:

В FORTRAN аргументы функций не могут быть псевдонимами друг друга, и компилятор предполагает, что они этого не делают. Это обеспечивает отличную оптимизацию и является одной из основных причин репутации FORTRAN как быстрого языка. (Обратите внимание, что псевдонимы все еще могут возникать внутри функции FORTRAN. Например, если A - это массив, а i и j - индексы, которые имеют одинаковое значение, то A [i] и A [j] - два разных имени для та же ячейка памяти. К счастью, поскольку базовый массив должен иметь одинаковое имя, можно выполнить анализ индекса, чтобы определить случаи, когда A [i] и A [j] не могут иметь псевдоним.)

Но я согласен с другими здесь: сравнивать среднее количество инструкций ассемблера, сгенерированных для строки кода, - полная чушь. Например, современное ядро ​​x86 может выполнять две инструкции параллельно, если они не обращаются к одним и тем же регистрам. Таким образом, вы можете (теоретически) повысить производительность на 100% для того же набора инструкций, просто переупорядочив их . Хорошие компиляторы также часто генерируют больше инструкций по сборке, чтобы получить более быстрый код (развернуть цикл, встроить). Общее количество инструкций ассемблера очень мало говорит о производительности фрагмента кода.

nikie
источник
Другая причина лучшей оптимизации - встроенная поддержка комплексных чисел.
SK-logic
Конечно, правильно для Fortran IV или около того. Не уверен, что современные фортраны до сих пор не имеют указателей, динамических функций и т. Д.
Ingo
2
Это та же самая причина, по которой мы часто прибегали к встроенной сборке при разработке на C и C ++ в игровой индустрии. Люди могут утверждать так часто, как им нравится, что «компиляторы могут оптимизировать лучше, чем люди, пишущие ассемблер», на самом деле, псевдонимы указателей означают, что они часто не могут . Код, который мы можем написать вручную, будет технически недопустимым для компилятора, поскольку он ничего не делает с псевдонимами указателей.
Carson63000
5
restrictКлючевое слово C позволяет автору функции указать, что указатель не имеет псевдонимов. Достаточно ли этого для устранения разницы или есть что-то большее?
БК
@bk .: C's "restrict" атакует "половину проблемы"; это позволяет сказать, что определенный указатель не будет псевдонимом чего-либо еще в течение своего времени жизни, но нет никакого способа сообщить компилятору, что объект, чей адрес был передан функции, не будет псевдонимом, когда эта функция вернется.
Суперкат
8

Полностью неверное сравнение.

Во-первых, как указывает @ Péter Török, вы должны сначала сравнить количество строк в эквивалентных программах от Fortran и C, чтобы это было даже допустимым сравнением количества произведенных строк.

Во-вторых, меньшее количество строк кода не всегда равно быстрым программам . Не все машинные инструкции выполняют одинаковое количество циклов , но у вас также есть другие проблемы, такие как доступ к памяти , кэширование и т. Д.

Кроме того, длинный код может выполняться быстрее, поскольку это приводит к меньшему количеству строк выполнения (т. Е. Line Count! = Executed Line Count ).

Дэн МакГрат
источник
5

Дэн прав, более длинные программы не означают более медленные программы. Это сильно зависит от того, что они делают.

Я не эксперт по Фортрану, я немного знаю. Сравнивая их, я бы подумал, что хорошо написанный C мог бы добиться гораздо большей производительности при более сложных структурах данных и функциональности, чем Fortran. Кто-то (пожалуйста) исправит меня, если я ошибаюсь, но я думаю, что Fortran находится на более низком уровне, чем C. Если это так, я уверен, что некоторые проблемы на Fortran возникнут быстрее.

Другое дело, на первый взгляд, я подумал, что вы спрашиваете, работают ли компиляторы быстрее. Я действительно думаю, что Fortran, как правило, будет компилироваться быстрее для схожих объемов кода, но итоговая программа и то, как она работает, будет другой историей. Проще разобрать.

Гарет Клаборн
источник
2
Если вы используете сложные структуры данных, FORTRAN, вероятно, неправильный выбор. FORTRAN оптимизирован, чтобы выполнять простые вычисления очень быстро.
Захари К
4

Я думаю, что отчасти это связано с тем, что компиляторы FORTRAN предназначены для очень быстрой математической обработки некоторых типов. Вот почему люди используют FORTRAN, чтобы делать вычисления как можно быстрее

Захари К
источник
4

Это утверждение могло быть правдой в старые времена (примерно в конце 70-х годов), когда C был в зачаточном состоянии, и Fortran поддерживался всеми крупными производителями и был высоко оптимизирован. Ранние Fortrans были основаны на архитектуре IBM, поэтому такие простые вещи, как арифметика, были бы, конечно, одним утверждением на инструкцию по сборке. Это верно для более старых машин, таких как Data General и Prime, которые имели 3-х ходовые прыжки. Это не работает с современными наборами команд, которые не имеют 3-х ходового перехода.

Строки кода не равны утверждениям кода. Более ранние версии Fortran допускали только одно утверждение на строку. Более поздние версии Fortran могут принимать несколько операторов в строке. C может иметь несколько операторов в строке. На более быстрых производственных компиляторах, таких как Intel IVF (ранее CVF, MS Powerstation) и Intel 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 пытался несколько улучшить ситуацию с C, добавив restrictклассификатор, он мог бы использоваться здесь только в том случае, если он dest[]был отдельным массивом от обоих src1[]и src2[], или если программист добавил отдельные версии цикла для обработки случаев, когда все destбыло не разделеноsrc1и src2, где src1[]и destбыли равны и src2не пересекались, где src2[]и dest[]были равны и src1не пересекались, и где все три массива были равны. В отличие от этого, FORTRAN может без проблем обрабатывать все четыре случая, используя один и тот же исходный код и один и тот же машинный код.

Supercat
источник