Этот вопрос является продолжением двух обсуждений, недавно появившихся в ответах на " C ++ против Fortran for HPC ". И это больше вызов, чем вопрос ...
Один из наиболее часто звучащих аргументов в пользу Fortran заключается в том, что компиляторы просто лучше. Поскольку большинство компиляторов C / Fortran используют один и тот же бэкэнд, код, сгенерированный для семантически эквивалентных программ на обоих языках, должен быть одинаковым. Однако можно утверждать, что компилятор C / Fortran более / менее удобен для оптимизации.
Поэтому я решил попробовать простой тест: я получил копии daxpy.f и daxpy.c и скомпилировал их с помощью gfortran / gcc.
Теперь daxpy.c - это всего лишь f2c-перевод daxpy.f (автоматически сгенерированный код, безобразный как черт), поэтому я взял этот код и немного его очистил (соответствует daxpy_c), что в основном означало переписать самый внутренний цикл как
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Наконец, я переписал его (введите daxpy_cvec), используя векторный синтаксис gcc:
#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, M560, 2,67 ГГц) я получил следующий вывод:
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.
Таким образом, исходный код на Фортране занимает чуть больше 8,1 секунды, его автоматический перевод занимает 10,5 секунд, наивная реализация C делает это в 7,9, а явно векторизованный код делает это в 5,6, незначительно меньше.
Это Fortran немного медленнее, чем простая реализация C, и на 50% медленнее, чем векторизованная реализация C.
Итак, вот вопрос: я - нативный программист на C, и поэтому я совершенно уверен, что я хорошо поработал над этим кодом, но последний раз код на Фортране был затронут в 1993 году и поэтому может быть немного устаревшим. Поскольку я не чувствую себя настолько комфортно в кодировании на Фортране, как другие, возможно, кто-то здесь, может ли кто-нибудь сделать лучшую работу, то есть более конкурентоспособную по сравнению с любой из двух версий C?
Кроме того, кто-нибудь может попробовать этот тест с icc / ifort? Синтаксис вектора, вероятно, не будет работать, но мне было бы любопытно посмотреть, как ведет себя наивная C-версия. То же самое касается любого, у кого xlc / xlf валяется.
Я загрузил исходники и Makefile здесь . Чтобы получить точные значения времени, установите CPU_TPS в test.c равным числу Гц на вашем процессоре. Если вы обнаружите какие-либо улучшения в любой из версий, пожалуйста, опубликуйте их здесь!
Обновить:
Я добавил тестовый код Стали к файлам онлайн и дополнил его версией C. Я изменил программы, чтобы сделать 1 000 000 циклов для векторов длиной 10 000, чтобы они соответствовали предыдущему тесту (и поскольку моя машина не могла выделить векторы длиной 1 000 000 000, как в исходном коде Стали код). Так как числа теперь немного меньше, я использовал опцию, -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 и добавил его в файлы в Интернете . Вот результаты тройного цикла для одного и двух процессоров:
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 измеряется время процессора, а не время настенных часов, поэтому я обернул вызовы, time
чтобы сравнить их для двух процессоров. Между результатами нет никакой реальной разницы, за исключением того, что версия C работает немного лучше на двух ядрах.
Теперь для matmul
команды, конечно, только в Фортране, поскольку эта внутренняя функция недоступна в 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
вызовы в эталонный тест, поскольку они являются вызовами библиотек для одной и той же функции в Intel MKL.
Для будущих тестов кто-нибудь может предложить пример, который, как известно, медленнее в C, чем в Fortran?
Обновить
Чтобы проверить утверждение Стали о том, что matmul
внутренняя величина «на порядок» быстрее, чем явное матричное произведение для меньших матриц, я модифицировал его собственный код для умножения матриц размером 100x100, используя оба метода, по 10 000 раз каждый. Результаты на одном и двух процессорах следующие:
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
Обновить
Грису прав в том, что без оптимизации gcc преобразует операции над комплексными числами в вызовы библиотечных функций, в то время как gfortran вставляет их в несколько инструкций.
Компилятор C сгенерирует тот же компактный код, если эта опция -fcx-limited-range
установлена, то есть компилятору дано указание игнорировать потенциальные избыточные / недостаточные потоки в промежуточных значениях. Эта опция каким-то образом установлена по умолчанию в gfortran и может привести к неверным результатам. Принуждение -fno-cx-limited-range
в гфортране ничего не изменило.
Так что на самом деле это аргумент против использования gfortran для численных расчетов: операции со сложными значениями могут быть чрезмерными / недостаточными, даже если правильные результаты находятся в пределах диапазона с плавающей запятой. Это на самом деле стандарт Фортрана. В gcc или в C99 в целом по умолчанию все делается строго (см. IEEE-754), если не указано иное.
Напоминание: пожалуйста, имейте в виду, что главный вопрос заключался в том, производят ли компиляторы Fortran лучший код, чем компиляторы Си. Здесь не место обсуждать общие достоинства одного языка перед другим. Что меня действительно заинтересовало бы, так это то, что кто-нибудь может найти способ заставить gfortran создать daxpy, столь же эффективный, как в C, с использованием явной векторизации, поскольку это иллюстрирует проблемы необходимости полагаться на компилятор исключительно для оптимизации SIMD, или случай, когда компилятор Фортрана превосходит свой аналог Си.