Це питання є продовженням двох дискусій, які нещодавно з'явилися у відповідях на " C ++ проти Fortran для 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.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, незначно менше.
Це Фортран трохи повільніше, ніж наївне реалізація С, і на 50% повільніше, ніж векторизована реалізація С.
Отже, ось питання: я рідний програміст на C, і тому я впевнений, що я зробив хорошу роботу над цим кодом, але код Fortran востаннє торкнувся в 1993 році і тому може бути трохи застарілим. Оскільки я не відчуваю себе комфортним кодуванням у Fortran, як інші, може хтось може зробити кращу роботу, тобто більш конкурентоспроможну порівняно з будь-якою з двох версій C?
Також хто-небудь може спробувати цей тест з icc / ifort? Векторний синтаксис, ймовірно, не спрацює, але мені було б цікаво побачити, як там поводиться наївна версія C. Те саме стосується тих, хто лежить навколо xlc / xlf.
Я завантажив вихідні коди і Makefile тут . Щоб отримати точні таймінги, встановіть CPU_TPS у test.c на кількість Гц на вашому процесорі. Якщо ви знайдете якісь вдосконалення будь-якої з версій, будь ласка, опублікуйте їх тут!
Оновлення:
Я додав тестовий код Stali до файлів в Інтернеті та доповнив його версією 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
щоб порівняти їх для 2 процесорів. Ніякої різниці між результатами немає, за винятком того, що версія 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
виклики до еталону, оскільки це дзвінки в бібліотеку до тієї ж функції в Intel MKL.
Чи може хтось запропонувати для майбутніх тестів приклад, який, як відомо, у C повільніше, ніж у Фортран?
Оновлення
Для перевірки твердження Сталі про те, що 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
Оновлення
Grisu вірно вказує, що без оптимізацій gcc перетворює операції над складними номерами у виклики функцій бібліотеки, тоді як gfortran накреслює їх у кількох інструкціях.
Компілятор C генерує той самий, компактний код, якщо параметр -fcx-limited-range
встановлений, тобто компілятору доручено ігнорувати потенційні перевищення / недоліки в проміжних значеннях. Цей параметр якимось чином встановлений за замовчуванням у gfortran і може призвести до неправильних результатів. Примушування -fno-cx-limited-range
в gfortran нічого не змінило.
Таким чином, це насправді аргумент проти використання gfortran для чисельних обчислень: Операції над складними значеннями можуть перевищувати / недолічувати, навіть якщо правильні результати знаходяться в діапазоні з плаваючою комою. Це насправді стандарт Fortran. У gcc або загалом у C99 за замовчуванням робиться все строго (читайте сумісність IEEE-754), якщо не вказано інше.
Нагадування: Будь ласка, майте на увазі, що головне питання полягав у тому, чи компілятори Fortran виробляють кращий код, ніж компілятори C. Це не місце для дискусій щодо загальних достоїнств однієї мови перед іншою. Мені б дуже цікаво, якщо хтось може знайти спосіб угамування gfortran для отримання дакпі настільки ефективний, як той на C, використовуючи явну векторизацію, оскільки це ілюструє проблеми необхідності покладатися на компілятор виключно для оптимізації SIMD або випадок, коли компілятор Fortran виходить зі свого аналога C.