Этот вопрос является продолжением двух обсуждений, недавно появившихся в ответах на " 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, или случай, когда компилятор Фортрана превосходит свой аналог Си.
источник
restrict
ключевое слово, которое точно указывает компилятору: предполагать, что массив не перекрывается с какой-либо другой структурой данных.Ответы:
Разница в ваших таймингах , кажется, из - за ручное разворачивание единичного-шаг Fortran daxpy . Следующие тайминги на 2,67 ГГц Xeon X5650, с использованием команды
Компиляторы Intel 11.1
Фортран с ручным развертыванием: 8,7 с
Фортран без ручного развертывания: 5,8 с
C без ручного развертывания: 5,8 с
Компиляторы GNU 4.1.2
Fortran с ручной разверткой: 8,3 с.
Fortran без ручной развертки: 13,5 с.
C без ручной развертки: 13,6 с.
C с векторными атрибутами: 5,8 с.
Компиляторы GNU 4.4.5
Fortran с ручной разверткой: 8,1 с.
Fortran без ручной развертки: 7,4 с.
C без ручной развертки: 8,5 с.
C с векторными атрибутами: 5,8 с.
Выводы
Время проверить более сложные процедуры, такие как dgemv и dgemm?
источник
Я опаздываю на эту вечеринку, поэтому мне трудно следить за всем этим. Вопрос большой, и я думаю, что если вам интересно, его можно разбить на более мелкие части. Одной вещью, которая меня заинтересовала, была просто производительность ваших
daxpy
вариантов и то, работает ли Fortran медленнее, чем C в этом очень простом коде.Работая как на моем ноутбуке (Macbook Pro, Intel Core i7, 2,66 ГГц), относительная производительность вашей версии C с ручной векторизацией и версии Fortran без векторизации зависит от используемого компилятора (с вашими опциями):
Таким образом, кажется, что GCC стал лучше векторизовать цикл в ветке 4.6, чем это было раньше.
В целом, я думаю, что можно написать быстрый и оптимизированный код как на C, так и на Fortran, почти как на ассемблере. Однако я укажу одну вещь: точно так же, как ассемблер писать труднее, чем C, но дает вам более точный контроль над тем, что исполняется процессором, C более низкоуровневый, чем Fortran. Таким образом, он дает вам больше контроля над деталями, что может помочь в оптимизации, когда стандартный синтаксис Fortran (или его расширения поставщика) может не иметь функциональности. Один случай - явное использование векторных типов, другой - возможность указания выравнивания переменных вручную, на что Фортран не способен.
источник
Способ написания AXPY на Фортране немного отличается. Это точный перевод математики.
m_blas.f90
Теперь давайте назовем вышеупомянутую подпрограмму в программе.
test.f90
Теперь давайте скомпилируем и запустим его ...
Обратите внимание, что я не использую никаких циклов или каких-либо явных директив OpenMP . Будет ли это возможно в C (то есть без использования циклов и автопараллелизации)? Я не использую C, поэтому я не знаю.
источник
icc
также выполняет автоматическое распараллеливание. Я добавил файлicctest.c
в другие источники. Можете ли вы скомпилировать его с теми же опциями, которые вы использовали выше, запустить его и сообщить о времени? Мне пришлось добавить оператор printf в мой код, чтобы избежать gcc оптимизации всего. Это просто быстрый взлом, и я надеюсь, что это без ошибок!Я думаю, не только интересно, как компилятор оптимизирует код для современного оборудования. Особенно между GNU C и GNU Fortran генерация кода может сильно отличаться.
Итак, давайте рассмотрим другой пример, чтобы показать различия между ними.
Используя комплексные числа, компилятор GNU C создает большие издержки для почти базовой арифметической операции над комплексным числом. Компилятор Фортрана дает гораздо лучший код. Давайте посмотрим на следующий небольшой пример на Фортране:
дает (gfortran -g -o complex.fo -c complex.f95; objdump -d -S complex.fo):
Это 39-байтовый машинный код. Когда мы рассматриваем то же самое в C
и взглянем на вывод (сделанный так же, как и выше), мы получим:
Это тоже 39-байтовый машинный код, но этап 57 функции относится к выполнению правильной части работы и выполнению требуемой операции. Итак, у нас есть 27-байтовый машинный код для запуска мультиоперации. Эта функция предоставлена muldc3
libgcc_s.so
и занимает 1375 байт в машинном коде. Это значительно замедляет код и дает интересный вывод при использовании профилировщика.Когда мы реализуем приведенные выше примеры BLAS
zaxpy
и выполняем один и тот же тест, компилятор Fortran должен давать лучшие результаты, чем компилятор C.(Я использовал GCC 4.4.3 для этого эксперимента, но я заметил это поведение в других версиях GCC.)
Так что, на мой взгляд, мы не только думаем о распараллеливании и векторизации, когда думаем о том, какой компилятор лучше, мы также должны смотреть, как базовые вещи транслируются в ассемблерный код. Если этот перевод дает плохой код, оптимизация может использовать только эти вещи в качестве входных данных.
источник
complex.c
и добавил его к коду онлайн. Мне пришлось добавить все входы / выходы, чтобы убедиться, что ничего не оптимизировано. Мне звонят только__muldc3
если я не пользуюсь-ffast-math
. С помощью-O2 -ffast-math
я получаю 9 строк встроенного ассемблера. Вы можете это подтвердить?-ffast-math
) вы не должны использовать Фортран для ваших комплексных вычислений. Как я описываю в обновлении моего вопроса,-ffast-math
или, в более общем смысле,-fcx-limited-range
вынуждает gcc использовать те же вычисления с ограниченным диапазоном, не соответствующие стандарту IEEE, которые являются стандартными в Fortran. Поэтому, если вам нужен полный диапазон комплексных значений и правильные значения Infs и NaN, вам не следует использовать Fortran ...Folks,
Я нашел это обсуждение очень интересным, но я был удивлен, увидев, что переупорядочивание циклов в примере с Matmul изменило картину. На моем компьютере нет компилятора Intel, поэтому я использую gfortran, но переписываю циклы в mm_test.f90 для
поменял весь результат для моей машины.
Предыдущие результаты синхронизации версии были:
тогда как с тройными петлями, переставленными как выше, получаются:
Это gcc / gfortran 4.7.2 20121109 на процессоре Intel® Core ™ ™ i7-2600K с частотой 3,40 ГГц
Использовались флаги компилятора из файла Makefile, который я получил здесь ...
источник
Это не языки, которые заставляют код работать быстрее, хотя они помогают. Компилятор, процессор и операционная система ускоряют выполнение кода. Сравнение языков - просто неправильное, бесполезное и бессмысленное. Это не имеет никакого смысла, потому что вы сравниваете две переменные: язык и компилятор. Если один код работает быстрее, вы не знаете, сколько это язык или сколько это компилятор. Я не понимаю, почему сообщество информатики просто не понимает этого :-(
источник