Saya melakukan beberapa pekerjaan penting kinerja di C ++, dan kami saat ini menggunakan perhitungan integer untuk masalah yang secara inheren floating point karena "lebih cepat". Ini menyebabkan banyak masalah yang mengganggu dan menambahkan banyak kode yang mengganggu.
Sekarang, saya ingat pernah membaca tentang bagaimana penghitungan floating point sangat lambat kira-kira sekitar 386 hari, di mana saya percaya (IIRC) bahwa ada ko-prosesor opsional. Tapi tentunya saat ini dengan CPU yang secara eksponensial lebih kompleks dan bertenaga tidak ada perbedaan dalam "kecepatan" jika melakukan perhitungan floating point atau integer? Terutama karena waktu kalkulasi yang sebenarnya sangat kecil dibandingkan dengan sesuatu seperti menyebabkan pipa berhenti atau mengambil sesuatu dari memori utama?
Saya tahu jawaban yang benar adalah dengan melakukan benchmark pada perangkat keras target, cara apa yang baik untuk menguji ini? Saya menulis dua program C ++ kecil dan membandingkan waktu berjalannya dengan "waktu" di Linux, tetapi waktu proses sebenarnya terlalu bervariasi (tidak membantu saya menjalankan di server virtual). Tanpa menghabiskan seluruh hari saya menjalankan ratusan tolok ukur, membuat grafik, dll. Adakah yang dapat saya lakukan untuk mendapatkan pengujian kecepatan relatif yang wajar? Ada ide atau pemikiran? Apakah saya sepenuhnya salah?
Program yang saya gunakan sebagai berikut, sama sekali tidak identik:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>
int main( int argc, char** argv )
{
int accum = 0;
srand( time( NULL ) );
for( unsigned int i = 0; i < 100000000; ++i )
{
accum += rand( ) % 365;
}
std::cout << accum << std::endl;
return 0;
}
Program 2:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <time.h>
int main( int argc, char** argv )
{
float accum = 0;
srand( time( NULL ) );
for( unsigned int i = 0; i < 100000000; ++i )
{
accum += (float)( rand( ) % 365 );
}
std::cout << accum << std::endl;
return 0;
}
Terima kasih sebelumnya!
Sunting: Platform yang saya pedulikan adalah x86 atau x86-64 biasa yang berjalan di desktop Linux dan mesin Windows.
Edit 2 (ditempelkan dari komentar di bawah): Saat ini kami memiliki basis kode yang luas. Sungguh saya telah menentang generalisasi bahwa kita "tidak boleh menggunakan float karena perhitungan integer lebih cepat" - dan saya sedang mencari cara (jika ini benar) untuk menyangkal asumsi umum ini. Saya menyadari bahwa tidak mungkin untuk memprediksi hasil yang tepat bagi kami tanpa melakukan semua pekerjaan dan membuat profil setelahnya.
Bagaimanapun, terima kasih atas semua jawaban dan bantuan Anda yang luar biasa. Jangan ragu untuk menambahkan yang lain :).
sumber
addl
diganti denganfadd
, misalnya). Satu-satunya cara untuk benar-benar mendapatkan pengukuran yang baik adalah mendapatkan bagian inti dari program Anda yang sebenarnya dan membuat profil versi yang berbeda dari itu. Sayangnya itu bisa sangat sulit tanpa menggunakan banyak usaha. Mungkin memberitahu kami target perangkat keras dan kompiler Anda akan membantu orang setidaknya memberi Anda pengalaman yang sudah ada sebelumnya, dll. Tentang penggunaan integer Anda, saya curiga Anda bisa membuat semacamfixed_point
kelas template yang akan sangat memudahkan pekerjaan seperti itu.float
mendapat peningkatan kecepatan, tetapi biasanyadouble
tidak.Jawaban:
Sayangnya, saya hanya bisa memberi Anda jawaban "itu tergantung" ...
Dari pengalaman saya, ada banyak, banyak variabel untuk kinerja ... terutama antara matematika integer & floating point. Ini sangat bervariasi dari prosesor ke prosesor (bahkan dalam keluarga yang sama seperti x86) karena prosesor yang berbeda memiliki panjang "pipa" yang berbeda. Selain itu, beberapa operasi umumnya sangat sederhana (seperti penambahan) dan memiliki rute yang dipercepat melalui prosesor, dan lainnya (seperti pembagian) membutuhkan waktu yang jauh lebih lama.
Variabel besar lainnya adalah tempat data berada. Jika Anda hanya memiliki sedikit nilai untuk ditambahkan, maka semua data dapat disimpan dalam cache, dan dapat dengan cepat dikirim ke CPU. Operasi floating point yang sangat, sangat lambat yang sudah memiliki data dalam cache akan berkali-kali lebih cepat daripada operasi integer di mana integer perlu disalin dari memori sistem.
Saya berasumsi bahwa Anda menanyakan pertanyaan ini karena Anda sedang mengerjakan aplikasi kinerja kritis. Jika Anda mengembangkan untuk arsitektur x86, dan Anda memerlukan kinerja ekstra, Anda mungkin ingin melihat ke dalam menggunakan ekstensi SSE. Hal ini dapat sangat mempercepat aritmatika floating point presisi tunggal, karena operasi yang sama dapat dilakukan pada beberapa data sekaligus, plus ada bank register * yang terpisah untuk operasi SSE. (Saya perhatikan dalam contoh kedua Anda menggunakan "float" bukan "double", membuat saya berpikir Anda menggunakan matematika presisi tunggal).
* Catatan: Menggunakan instruksi MMX lama sebenarnya akan memperlambat program, karena instruksi lama tersebut sebenarnya menggunakan register yang sama seperti yang dilakukan FPU, sehingga tidak mungkin untuk menggunakan FPU dan MMX secara bersamaan.
sumber
double
-precision FP. Dengan hanya dua 64-bitdouble
s per register, speedup potensial lebih kecil daripadafloat
kode yang melakukan vektorisasi dengan baik. Skalarfloat
dandouble
gunakan register XMM di x86-64, dengan x87 lama hanya digunakan untuklong double
. (Jadi @ Dan: tidak, register MMX tidak bertentangan dengan register FPU normal, karena FPU normal pada x86-64 adalah unit SSE. MMX tidak ada gunanya karena jika Anda dapat melakukan SIMD integer, Anda menginginkan 16-byte,xmm0..15
bukan 8 -bytemm0..7
, dan CPU modern memiliki MMX yang lebih buruk daripada throughput SSE.)Misalnya (angka yang lebih kecil lebih cepat),
Intel Xeon X5550 64-bit @ 2.67GHz, gcc 4.1.2
-O3
Prosesor AMD Opteron (tm) Dual Core 32-bit 265 @ 1.81GHz, gcc 3.4.6
-O3
Seperti yang ditunjukkan Dan , bahkan setelah Anda menormalkan frekuensi clock (yang dapat menyesatkan dirinya sendiri dalam desain pipelined), hasilnya akan sangat bervariasi berdasarkan arsitektur CPU ( kinerja ALU / FPU individual , serta jumlah aktual ALU / FPU yang tersedia per inti dalam desain superscalar yang memengaruhi berapa banyak operasi independen yang dapat dijalankan secara paralel - faktor terakhir tidak dijalankan oleh kode di bawah ini karena semua operasi di bawah ini bergantung secara berurutan.)
Tolok ukur operasi FPU / ALU orang miskin:
sumber
volatile
untuk memastikan. Di Win64, FPU tidak digunakan dan MSVC tidak akan menghasilkan kode untuk itu, jadi ia mengkompilasi menggunakanmulss
dandivss
instruksi XMM di sana, yang 25x lebih cepat daripada FPU di Win32. Mesin uji adalah Core i5 M 520 @ 2.40GHzv
akan dengan cepat mencapai 0 atau +/- inf dengan sangat cepat, yang mungkin (secara teoritis) diperlakukan sebagai kasus khusus / fastpatheed oleh implementasi fpu tertentu.v
) yang sama. Pada desain Intel baru-baru ini, pembagian sama sekali tidak dipipeline (divss
/divps
memiliki latensi siklus 10-14, dan throughput timbal balik yang sama).mulss
bagaimanapun adalah 5 siklus latensi, tetapi dapat mengeluarkan satu setiap siklus. (Atau dua per siklus di Haswell, karena port 0 dan port 1 keduanya memiliki pengganda untuk FMA).Mungkin ada perbedaan yang signifikan dalam kecepatan dunia nyata antara matematika fixed-point dan floating-point, tetapi throughput kasus terbaik teoritis dari ALU vs FPU sama sekali tidak relevan. Sebagai gantinya, jumlah register integer dan floating-point (register nyata, bukan nama register) pada arsitektur Anda yang tidak digunakan oleh komputasi Anda (misalnya untuk kontrol loop), jumlah elemen dari setiap jenis yang sesuai dalam baris cache , pengoptimalan dapat dilakukan dengan mempertimbangkan semantik yang berbeda untuk matematika integer vs. floating point - efek ini akan mendominasi. Ketergantungan data algoritme Anda memainkan peran penting di sini, sehingga tidak ada perbandingan umum yang akan memprediksi kesenjangan kinerja pada masalah Anda.
Misalnya, penambahan integer bersifat komutatif, jadi jika compiler melihat loop seperti yang Anda gunakan untuk benchmark (dengan asumsi data acak disiapkan sebelumnya sehingga tidak akan mengaburkan hasil), compiler dapat membuka loop dan menghitung jumlah parsial dengan tidak ada dependensi, lalu tambahkan ketika loop berakhir. Tetapi dengan floating point, kompilator harus melakukan operasi dalam urutan yang sama yang Anda minta (Anda memiliki poin urutan di sana sehingga kompilator harus menjamin hasil yang sama, yang melarang pengubahan urutan) sehingga ada ketergantungan yang kuat dari setiap penambahan pada hasil dari yang sebelumnya.
Anda juga cenderung memasukkan lebih banyak operan integer dalam cache pada satu waktu. Jadi, versi titik tetap mungkin mengungguli versi float dengan urutan besarnya bahkan pada mesin di mana FPU secara teoritis memiliki throughput yang lebih tinggi.
sumber
Penambahan jauh lebih cepat daripada
rand
, jadi program Anda (terutama) tidak berguna.Anda perlu mengidentifikasi hotspot kinerja dan secara bertahap mengubah program Anda. Sepertinya Anda memiliki masalah dengan lingkungan pengembangan Anda yang perlu diselesaikan terlebih dahulu. Apakah tidak mungkin untuk menjalankan program Anda pada PC Anda karena masalah kecil?
Umumnya, mencoba pekerjaan FP dengan aritmatika integer adalah resep untuk lambat.
sumber
timespec_t
atau yang serupa. Catat waktu di awal dan akhir putaran dan ambil perbedaannya. Kemudian pindahkan pembuatanrand
data keluar dari loop. Pastikan algoritme Anda mendapatkan semua datanya dari array dan meletakkan semua datanya dalam array. Itu mendapatkan algoritme Anda yang sebenarnya dengan sendirinya, dan mendapatkan pengaturan, malloc, pencetakan hasil, semuanya kecuali pengalihan tugas dan interupsi dari loop profil Anda.TIL Ini bervariasi (banyak). Berikut adalah beberapa hasil menggunakan kompiler gnu (btw saya juga diperiksa dengan mengompilasi pada mesin, gnu g ++ 5.4 dari xenial jauh lebih cepat dari 4.6.3 dari linaro pada tepatnya)
Intel i7 4700MQ xenial
Intel i3 2370M memiliki hasil yang serupa
Intel (R) Celeron (R) 2955U (Acer C720 Chromebook menjalankan xenial)
DigitalOcean 1GB Droplet Intel (R) Xeon (R) CPU E5-2630L v2 (berjalan terpercaya)
Prosesor AMD Opteron (tm) 4122 (presisi)
Ini menggunakan kode dari http://pastebin.com/Kx8WGUfg sebagai
benchmark-pc.c
Saya telah menjalankan beberapa lintasan, tetapi ini tampaknya kasus di mana angka umumnya sama.
Satu pengecualian penting tampaknya adalah ALU mul vs FPU mul. Penjumlahan dan pengurangan tampaknya sangat berbeda.
Berikut ini dalam bentuk grafik (klik untuk ukuran penuh, lebih rendah lebih cepat dan lebih disukai):
Pembaruan untuk mengakomodasi @Peter Cordes
https://gist.github.com/Lewiscowles1986/90191c59c9aedf3d08bf0b129065cccc
i7 4700MQ Linux Ubuntu Xenial 64-bit (semua patch hingga 2018-03-13 diterapkan) Prosesor AMD Opteron (tm) 4122 (presisi, hosting bersama DreamHost) Intel Xeon E5-2630L v2 @ 2.4GHz (64-bit terpercaya, DigitalOcean VPS)sumber
benchmark-pc
mengukur beberapa kombinasi throughput dan latensi? Pada Haswell (i7 4700MQ) Anda, perkalian integer adalah 1 throughput per jam, 3 latensi siklus, tetapi penambahan / sub integer adalah 4 throughput per jam, 1 latensi siklus ( agner.org/optimize ). Jadi mungkin ada banyak overhead loop yang menipiskan angka-angka itu untuk add dan mul menjadi sangat dekat (long add: 0.824088 vs. long mul: 1.017164). (default gcc untuk tidak melepaskan putaran, kecuali untuk melepas gulungan sepenuhnya jumlah iterasi yang sangat rendah).int
, hanyashort
danlong
? Di Linux x86-64,short
adalah 16 bit (dan karenanya memiliki penurunan register parsial dalam beberapa kasus), sedangkanlong
danlong long
keduanya merupakan tipe 64-bit. (Mungkin dirancang untuk Windows di mana x86-64 masih menggunakan 32-bitlong
? Atau mungkin dirancang untuk mode 32-bit.) Di Linux, x32 ABI memiliki 32-bitlong
dalam mode 64-bit , jadi jika Anda menginstal pustaka , gunakangcc -mx32
untuk menyusun untuk ILP32. Atau gunakan saja-m32
dan lihatlong
angkanya.addps
register xmm sebagai gantiaddss
, untuk melakukan 4 FP menambahkan secara paralel dalam satu instruksi yang secepat skalaraddss
. (Gunakan-march=native
untuk mengizinkan penggunaan set instruksi apa pun yang didukung CPU Anda, bukan hanya baseline SSE2 untuk x86-64).Dua hal yang perlu dipertimbangkan -
Perangkat keras modern dapat tumpang tindih dengan instruksi, menjalankannya secara paralel dan menyusunnya kembali untuk memanfaatkan perangkat keras dengan sebaik-baiknya. Dan juga, program floating point yang signifikan cenderung memiliki kerja integer yang signifikan juga meskipun itu hanya menghitung indeks ke dalam array, penghitung loop, dll. Jadi bahkan jika Anda memiliki instruksi floating point yang lambat, program itu mungkin berjalan pada perangkat keras yang terpisah tumpang tindih dengan beberapa pekerjaan integer. Maksud saya adalah bahwa meskipun instruksi floating point lambat daripada integer, program Anda secara keseluruhan dapat berjalan lebih cepat karena dapat menggunakan lebih banyak perangkat keras.
Seperti biasa, satu-satunya cara untuk memastikan adalah membuat profil program Anda yang sebenarnya.
Poin kedua adalah sebagian besar CPU saat ini memiliki instruksi SIMD untuk floating point yang dapat beroperasi pada beberapa nilai floating point pada waktu yang bersamaan. Misalnya Anda dapat memuat 4 float ke dalam satu register SSE dan melakukan 4 perkalian pada semuanya secara paralel. Jika Anda dapat menulis ulang bagian dari kode Anda untuk menggunakan instruksi SSE, kemungkinan besar itu akan lebih cepat daripada versi integer. Visual c ++ menyediakan fungsi intrinsik kompilator untuk melakukan ini, lihat http://msdn.microsoft.com/en-us/library/x5c07e2a(v=VS.80).aspx untuk beberapa informasi.
sumber
Versi floating point akan jauh lebih lambat, jika tidak ada operasi yang tersisa. Karena semua penambahan berurutan, cpu tidak akan dapat memparalelkan penjumlahan. Latensi akan sangat penting. FPU menambahkan latensi biasanya 3 siklus, sedangkan penambahan integer adalah 1 siklus. Namun, pembagi untuk operator lainnya mungkin akan menjadi bagian penting, karena tidak sepenuhnya terhubung dengan pipeline pada cpu modern. jadi, dengan asumsi instruksi pembagian / sisa akan menghabiskan sebagian besar waktu, perbedaan karena menambahkan latensi akan kecil.
sumber
Kecuali Anda menulis kode yang akan dipanggil jutaan kali per detik (seperti, misalnya, menggambar garis ke layar dalam aplikasi grafik), aritmatika integer vs. floating-point jarang menjadi penghambat.
Langkah pertama yang biasa untuk pertanyaan efisiensi adalah membuat profil kode Anda untuk melihat di mana waktu proses sebenarnya dihabiskan. Perintah linux untuk ini adalah
gprof
.Edit:
Meskipun saya kira Anda selalu dapat mengimplementasikan algoritme gambar garis menggunakan bilangan bulat dan angka floating-point, sebut saja berkali-kali dan lihat apakah itu membuat perbedaan:
http://en.wikipedia.org/wiki/Bresenham's_algorithm
sumber
Saat ini, operasi integer biasanya sedikit lebih cepat daripada operasi floating point. Jadi jika Anda dapat melakukan kalkulasi dengan operasi yang sama dalam integer dan floating point, gunakan integer. NAMUN Anda mengatakan "Ini menyebabkan banyak masalah yang mengganggu dan menambahkan banyak kode yang mengganggu". Kedengarannya Anda membutuhkan lebih banyak operasi karena Anda menggunakan aritmatika integer dan bukan floating point. Dalam hal ini floating point akan berjalan lebih cepat karena
segera setelah Anda membutuhkan lebih banyak operasi bilangan bulat, Anda mungkin membutuhkan lebih banyak, jadi sedikit keunggulan kecepatan lebih dari dimakan oleh operasi tambahan
kode floating-point lebih sederhana, yang berarti lebih cepat untuk menulis kode, yang berarti bahwa jika sangat penting kecepatan, Anda dapat menghabiskan lebih banyak waktu untuk mengoptimalkan kode.
sumber
Saya menjalankan tes yang baru saja menambahkan 1 ke angka, bukan rand (). Hasil (pada x86-64) adalah:
sumber
Berdasarkan "sesuatu yang pernah saya dengar" yang sangat dapat diandalkan itu, di masa lalu, penghitungan bilangan bulat sekitar 20 hingga 50 kali lebih cepat dari titik mengambang itu, dan saat ini kurang dari dua kali lebih cepat.
sumber