Pertanyaan ini merupakan perpanjangan dari dua diskusi yang muncul baru-baru ini di balasan untuk " C ++ vs Fortran untuk HPC ". Dan ini sedikit lebih menantang daripada sebuah pertanyaan ...
Salah satu argumen yang paling sering terdengar dalam mendukung Fortran adalah bahwa penyusunnya lebih baik. Karena sebagian besar kompiler C / Fortran berbagi ujung belakang yang sama, kode yang dihasilkan untuk program semantik yang setara dalam kedua bahasa harus identik. Satu dapat berpendapat, bagaimanapun, bahwa C / Fortran lebih / kurang mudah untuk kompiler untuk mengoptimalkan.
Jadi saya memutuskan untuk mencoba tes sederhana: Saya mendapat salinan daxpy.f dan daxpy.c dan mengompilasinya dengan gfortran / gcc.
Sekarang daxpy.c hanyalah terjemahan f2c dari daxpy.f (kode yang dibuat secara otomatis, jelek sekali), jadi saya mengambil kode itu dan membersihkannya sedikit (ketemu daxpy_c), yang pada dasarnya berarti menulis ulang loop paling dalam sebagai
for ( i = 0 ; i < n ; i++ )
dy[i] += da * dx[i];
Akhirnya, saya menulis ulang (masukkan daxpy_cvec) menggunakan sintaks vektor 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];
Perhatikan bahwa saya menggunakan vektor dengan panjang 2 (itu semua memungkinkan SSE2) dan saya memproses dua vektor sekaligus. Ini karena pada banyak arsitektur, kita mungkin memiliki unit multiplikasi lebih banyak daripada elemen vektor.
Semua kode dikompilasi menggunakan gfortran / gcc versi 4.5 dengan flag "-O3 -Wall -msse2 -march = asli -fast-matematika -fomit-frame-pointer -malign-double -fstrict-aliasing". Di laptop saya (Intel Core i5 CPU, M560, 2.67GHz) saya mendapat output sebagai berikut:
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.
Jadi kode Fortran asli membutuhkan waktu lebih dari 8,1 detik, terjemahan otomatisnya membutuhkan waktu 10,5 detik, implementasi C naif melakukannya di 7,9 dan kode vektor secara eksplisit melakukannya dalam 5,6, sedikit kurang.
Itu Fortran sedikit lebih lambat dari implementasi C naif dan 50% lebih lambat dari implementasi C vektor.
Jadi inilah pertanyaannya: Saya seorang programmer C asli dan jadi saya cukup yakin bahwa saya melakukan pekerjaan dengan baik pada kode itu, tetapi kode Fortran terakhir kali disentuh pada tahun 1993 dan karenanya mungkin agak ketinggalan zaman. Karena saya tidak merasa nyaman dengan pengkodean di Fortran seperti yang dilakukan orang lain di sini, adakah yang bisa melakukan pekerjaan yang lebih baik, yaitu lebih kompetitif dibandingkan dengan salah satu dari dua versi C?
Adakah yang bisa mencoba tes ini dengan icc / ifort? Sintaks vektor mungkin tidak akan berfungsi, tetapi saya akan penasaran untuk melihat bagaimana versi C naif berperilaku di sana. Hal yang sama berlaku untuk siapa pun dengan xlc / xlf berbaring.
Saya telah mengunggah sumber dan Makefile di sini . Untuk mendapatkan ketepatan waktu, atur CPU_TPS di test.c ke jumlah Hz di CPU Anda. Jika Anda menemukan peningkatan pada salah satu versi, silakan posting di sini!
Memperbarui:
Saya telah menambahkan kode uji stali ke file online dan menambahkannya dengan versi C. Saya memodifikasi program untuk melakukan 1'000'000 loop pada vektor dengan panjang 10'000 agar konsisten dengan tes sebelumnya (dan karena mesin saya tidak dapat mengalokasikan vektor dengan panjang 1'000'000'000, seperti pada stali's original kode). Karena jumlahnya sekarang sedikit lebih kecil, saya menggunakan opsi -par-threshold:50
untuk membuat kompiler lebih mungkin untuk diparalelkan. Versi icc / ifort yang digunakan adalah 12.1.2 20111128 dan hasilnya adalah sebagai berikut
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
Singkatnya, hasilnya, untuk semua tujuan praktis, identik untuk versi C dan Fortran, dan kedua kode diparalelkan secara otomatis. Perhatikan bahwa waktu yang cepat dibandingkan dengan pengujian sebelumnya adalah karena penggunaan aritmatika titik apung presisi tunggal!
Memperbarui:
Meskipun saya tidak begitu suka di mana beban pembuktian akan terjadi di sini, saya telah mengkodekan ulang contoh perkalian matriks stali di C dan menambahkannya ke file di web . Berikut ini adalah hasil dari tripple loop untuk satu dan dua CPU:
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
Perhatikan bahwa cpu_time
dalam Fortran mengukur waktu CPU dan bukan waktu jam dinding, jadi saya membungkus panggilan time
untuk membandingkannya dengan 2 CPU. Tidak ada perbedaan nyata antara hasil, kecuali bahwa versi C melakukan sedikit lebih baik pada dua core.
Sekarang untuk matmul
perintah, tentu saja hanya di Fortran karena intrinsik ini tidak tersedia dalam 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
Wow. Benar-benar mengerikan. Adakah yang bisa menemukan kesalahan saya, atau menjelaskan mengapa intrinsik ini masih merupakan hal yang baik?
Saya tidak menambahkan dgemm
panggilan ke patokan karena mereka panggilan perpustakaan ke fungsi yang sama di Intel MKL.
Untuk tes selanjutnya, adakah yang bisa menyarankan contoh yang diketahui lebih lambat dalam C daripada di Fortran?
Memperbarui
Untuk memverifikasi klaim stali bahwa matmul
intrinsiknya adalah "urutan magnitue" lebih cepat daripada produk matriks eksplisit pada matriks yang lebih kecil, saya memodifikasi kodenya sendiri untuk melipatgandakan matriks ukuran 100x100 menggunakan kedua metode, masing-masing 10'000 kali. Hasilnya, pada satu dan dua CPU, adalah sebagai berikut:
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
Memperbarui
Grisu benar dalam menunjukkan bahwa, tanpa optimisasi, gcc mengubah operasi pada bilangan kompleks menjadi panggilan fungsi pustaka sementara gfortran menyatukannya dalam beberapa instruksi.
Kompiler C akan menghasilkan kode ringkas yang sama jika opsi -fcx-limited-range
disetel, yaitu kompiler diinstruksikan untuk mengabaikan potensi kelebihan / kekurangan aliran dalam nilai-nilai perantara. Opsi ini entah bagaimana diatur secara default di gfortran dan dapat menyebabkan hasil yang salah. Memaksa -fno-cx-limited-range
gfortran tidak mengubah apa pun.
Jadi ini sebenarnya adalah argumen yang menentang penggunaan gfortran untuk perhitungan numerik: Operasi pada nilai kompleks mungkin over / under-flow bahkan jika hasil yang benar berada dalam kisaran floating-point. Ini sebenarnya adalah standar Fortran. Di gcc, atau di C99 secara umum, defaultnya adalah melakukan hal-hal dengan ketat (baca IEEE-754 compliant) kecuali ditentukan lain.
Pengingat: Harap diingat bahwa pertanyaan utama adalah apakah kompiler Fortran menghasilkan kode yang lebih baik daripada kompiler C. Ini bukan tempat untuk diskusi tentang manfaat umum dari satu bahasa di atas yang lain. Apa yang saya akan benar-benar tertarik adalah jika ada yang bisa menemukan cara membujuk gfortran untuk menghasilkan daxpy seefisien yang ada di C menggunakan vektorisasi eksplisit karena ini mencontohkan masalah harus bergantung pada kompiler khusus untuk optimasi SIMD, atau kasus di mana kompiler Fortran keluar-melakukan rekan C-nya.
sumber
restrict
kata kunci yang memberitahu kompiler persis bahwa: untuk menganggap bahwa array tidak tumpang tindih dengan struktur data lainnya.Jawaban:
Perbedaan dalam timing Anda tampaknya karena manual membuka gulungan Fortran daxpy unit-stride . Pengaturan waktu berikut ini pada 2,67 GHz Xeon X5650, menggunakan perintah
Kompiler Intel 11.1
Fortran dengan membuka gulungan manual: 8,7 detik
Fortran tanpa membuka gulungan manual: 5,8 detik
C tanpa membuka gulungan manual: 5,8 detik
Kompiler GNU 4.1.2
Fortran dengan membuka gulungan manual: 8,3 detik
Fortran tanpa membuka gulungan manual: 13,5 detik
C tanpa membuka gulungan manual: 13,6 detik
C dengan atribut vektor: 5,8 detik
Kompiler GNU 4.4.5
Fortran dengan membuka gulungan manual: 8,1 detik
Fortran tanpa membuka gulungan manual: 7,4 dtk
w / o membuka gulungan manual: 8,5 sec
C dengan atribusi vektor: 5,8 dtk
Kesimpulan
Waktu untuk menguji rutinitas yang lebih rumit seperti dgemv dan dgemm?
sumber
Saya datang terlambat ke pesta ini, jadi sulit bagi saya untuk mengikuti bolak-balik dari semua di atas. Pertanyaannya besar, dan saya pikir jika Anda tertarik itu bisa dipecah menjadi potongan-potongan kecil. Satu hal yang membuat saya tertarik hanyalah kinerja
daxpy
varian Anda , dan apakah Fortran lebih lambat dari C pada kode yang sangat sederhana ini.Menjalankan keduanya di laptop saya (Macbook Pro, Intel Core i7, 2.66 GHz), kinerja relatif dari versi C tangan-vektor Anda dan versi Fortran yang tidak di-vektorisasi tangan bergantung pada kompiler yang digunakan (dengan pilihan Anda sendiri):
Jadi, sepertinya GCC menjadi lebih baik dalam vektorisasi loop di cabang 4,6 daripada sebelumnya.
Pada debat keseluruhan, saya pikir seseorang dapat menulis kode yang cepat dan dioptimalkan baik dalam C dan Fortran, hampir seperti dalam bahasa assembly. Saya akan menunjukkan, bagaimanapun, satu hal: seperti assembler lebih membosankan untuk menulis daripada C tetapi memberi Anda kontrol yang lebih baik atas apa yang dieksekusi oleh CPU, C lebih rendah daripada Fortran. Dengan demikian, ini memberi Anda lebih banyak kontrol atas detail, yang dapat membantu mengoptimalkan, di mana sintaks standar Fortran (atau ekstensi vendornya) mungkin kurang fungsional. Satu kasus adalah penggunaan eksplisit dari jenis vektor, yang lain adalah kemungkinan menentukan keselarasan variabel dengan tangan, sesuatu yang Fortran tidak mampu.
sumber
Cara saya menulis AXPY dalam Fortran sedikit berbeda. Ini adalah terjemahan matematika yang tepat.
m_blas.f90
Sekarang mari kita panggil rutin di atas dalam sebuah program.
test.f90
Sekarang mari kita kompilasi dan jalankan ...
Perhatikan bahwa saya tidak menggunakan loop apa pun atau arahan OpenMP yang eksplisit . Apakah ini mungkin di C (yaitu, tidak ada penggunaan loop dan auto-parallelization)? Saya tidak menggunakan C jadi saya tidak tahu.
sumber
icc
juga melakukan paralelisasi otomatis. Saya telah menambahkan fileicctest.c
ke sumber lain. Bisakah Anda mengompilasinya dengan opsi yang sama seperti yang Anda gunakan di atas, menjalankannya, dan melaporkan waktunya? Saya harus menambahkan printf-statement ke kode saya untuk menghindari gcc mengoptimalkan semuanya. Ini hanya hack cepat dan saya harap ini bebas bug!Saya pikir, tidak hanya menarik bagaimana kompiler mengoptimalkan kode untuk perangkat keras modern. Terutama antara GNU C dan GNU Fortran pembuatan kode bisa sangat berbeda.
Jadi mari kita pertimbangkan contoh lain untuk menunjukkan perbedaan di antara mereka.
Menggunakan bilangan kompleks, kompiler GNU C menghasilkan overhead yang besar untuk operasi aritmatika yang hampir sangat mendasar pada bilangan kompleks. Kompiler Fortran memberikan kode yang jauh lebih baik. Mari kita lihat contoh kecil berikut ini di Fortran:
memberikan (gfortran -g -o complex.fo -c complex.f95; objdump -d -S complex.fo):
Yang merupakan kode mesin 39 byte. Ketika kita mempertimbangkan hal yang sama dalam C
dan lihat outputnya (dilakukan dengan cara yang sama seperti di atas), kita mendapatkan:
Yang merupakan kode mesin 39 byte juga, tetapi langkah fungsi 57 merujuk ke, melakukan bagian pekerjaan yang tepat dan melakukan operasi yang diinginkan. Jadi kami memiliki kode mesin 27 byte untuk menjalankan operasi multi. Fungsi di belakang adalah muldc3 yang disediakan oleh
libgcc_s.so
dan memiliki jejak 1375 byte dalam kode mesin. Ini memperlambat kode secara dramatis dan memberikan output yang menarik saat menggunakan profiler.Ketika kami menerapkan contoh BLAS di atas untuk
zaxpy
dan melakukan pengujian yang sama, kompiler Fortran harus memberikan hasil yang lebih baik daripada kompiler C.(Saya menggunakan GCC 4.4.3 untuk percobaan ini, tetapi saya perhatikan perilaku ini yang dirilis oleh GCC lainnya.)
Jadi menurut saya kita tidak hanya berpikir tentang paralelisasi dan vektorisasi ketika kita memikirkan mana yang merupakan kompiler yang lebih baik, kita juga harus melihat bagaimana hal-hal dasar diterjemahkan ke kode assembler. Jika terjemahan ini memberikan kode yang buruk optimasi hanya dapat menggunakan hal-hal ini sebagai input.
sumber
complex.c
dan menambahkannya ke kode online. Saya harus menambahkan semua input / output untuk memastikan tidak ada yang dioptimalkan. Saya hanya menerima telepon__muldc3
jika saya tidak menggunakannya-ffast-math
. Dengan-O2 -ffast-math
saya mendapatkan 9 baris assembler inline. Bisakah Anda mengkonfirmasi ini?-ffast-math
) Anda seharusnya tidak menggunakan Fortran untuk perhitungan Anda yang bernilai kompleks. Seperti yang saya jelaskan dalam pembaruan pertanyaan saya,-ffast-math
atau, lebih umum-fcx-limited-range
, memaksa gcc untuk menggunakan non-IEEE yang sama, perhitungan rentang terbatas seperti standar di Fortran. Jadi jika Anda ingin berbagai nilai kompleks dan mengoreksi Inf dan NaN, Anda tidak boleh menggunakan Fortran ...Orang-orang,
Saya menemukan diskusi ini sangat menarik, tetapi saya terkejut melihat bahwa memesan kembali loop dalam contoh Matmul mengubah gambar. Saya tidak memiliki kompiler intel yang tersedia di mesin saya saat ini, jadi saya menggunakan gfortran, tetapi menulis ulang loop di mm_test.f90 untuk
mengubah seluruh hasil untuk mesin saya.
Hasil waktu versi sebelumnya adalah:
sedangkan dengan triple loop diatur ulang seperti di atas yeilded:
Ini adalah gcc / gfortran 4.7.2 20121109 pada Intel (R) Core (TM) i7-2600K CPU @ 3.40GHz
Bendera kompiler yang digunakan adalah bendera dari Makefile yang saya dapatkan di sini ...
sumber
Bukan bahasa yang membuat kode berjalan lebih cepat, meskipun mereka membantu. Ini adalah kompiler, CPU dan sistem operasi yang membuat kode berjalan lebih cepat. Membandingkan bahasa hanya keliru, tidak berguna dan tidak berarti. Sama sekali tidak masuk akal karena Anda membandingkan dua variabel: bahasa dan kompiler. Jika satu kode berjalan lebih cepat, Anda tidak tahu berapa banyak bahasa atau berapa kompilernya. Saya tidak mengerti mengapa komunitas ilmu komputer tidak mengerti ini :-(
sumber