Perhitungan floating point vs integer pada perangkat keras modern

100

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 :).

maxpenguin.dll
sumber
8
Apa yang Anda miliki sebagai ujian Anda sekarang adalah sepele. Mungkin juga ada sedikit perbedaan dalam perakitan, ( addldiganti dengan fadd, 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 semacam fixed_pointkelas template yang akan sangat memudahkan pekerjaan seperti itu.
GManNickG
1
Masih banyak arsitektur di luar sana yang tidak memiliki perangkat keras floating point khusus - beberapa tag yang menjelaskan sistem yang Anda pedulikan akan membantu Anda mendapatkan jawaban yang lebih baik.
Carl Norum
3
Saya yakin perangkat keras di HTC Hero (android) saya tidak memiliki FPU, tetapi perangkat keras di Google NexusOne (android) memilikinya. apa target anda? PC desktop / server? netbook (kemungkinan arm + linux)? telepon?
SteelBytes
5
Jika Anda ingin FP cepat pada x86, coba kompilasi dengan optimasi dan pembuatan kode SSE. SSE (versi apa pun) dapat melakukan setidaknya penambahan, pengurangan, dan perkalian float dalam satu siklus. Bagi, mod, dan fungsi yang lebih tinggi akan selalu lambat. Perhatikan juga bahwa floatmendapat peningkatan kecepatan, tetapi biasanya doubletidak.
Mike D.
1
Bilangan bulat titik tetap mendekati FP dengan menggunakan beberapa operasi bilangan bulat untuk menjaga agar hasil tidak meluap. Itu hampir selalu lebih lambat daripada hanya menggunakan FPU yang sangat mumpuni yang ditemukan di CPU desktop modern. misalnya MAD, dekoder mp3 titik tetap, lebih lambat dari libmpg123, dan meskipun kualitasnya bagus untuk dekoder titik tetap, libmpg123 masih memiliki sedikit kesalahan pembulatan. wezm.net/technical/2008/04/mp3-decoder-libraries-dibandingkan untuk benchmark pada PPC G5.
Peter Cordes

Jawaban:

35

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.

Dan
sumber
8
Dan pada beberapa prosesor, matematika FP bisa lebih cepat daripada matematika integer. Prosesor Alpha memiliki instruksi pembagian FP tetapi bukan integer, sehingga pembagian integer harus dilakukan dalam perangkat lunak.
Gabe
Akankah SSEx juga mempercepat aritmatika presisi ganda? Maaf, saya tidak terlalu mengenal SSE
Johannes Schaub - litb
1
@ JohannesSchaub-litb: SSE2 (baseline untuk x86-64) telah mengemas double-precision FP. Dengan hanya dua 64-bit doubles per register, speedup potensial lebih kecil daripada floatkode yang melakukan vektorisasi dengan baik. Skalar floatdan doublegunakan register XMM di x86-64, dengan x87 lama hanya digunakan untuk long 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..15bukan 8 -byte mm0..7, dan CPU modern memiliki MMX yang lebih buruk daripada throughput SSE.)
Peter Cordes
1
Tetapi instruksi bilangan bulat MMX dan SSE * / AVX2 bersaing untuk unit eksekusi yang sama, jadi menggunakan keduanya sekaligus hampir tidak pernah berguna. Cukup gunakan versi XMM / YMM yang lebih luas untuk menyelesaikan lebih banyak pekerjaan. Menggunakan bilangan bulat SIMD dan FP pada saat yang sama bersaing untuk register yang sama, tetapi x86-64 memiliki 16 di antaranya. Tetapi batas total throughput berarti Anda tidak bisa menyelesaikan pekerjaan dua kali lebih banyak dengan menggunakan unit eksekusi integer dan FP secara paralel.
Peter Cordes
49

Misalnya (angka yang lebih kecil lebih cepat),

Intel Xeon X5550 64-bit @ 2.67GHz, gcc 4.1.2 -O3

short add/sub: 1.005460 [0]
short mul/div: 3.926543 [0]
long add/sub: 0.000000 [0]
long mul/div: 7.378581 [0]
long long add/sub: 0.000000 [0]
long long mul/div: 7.378593 [0]
float add/sub: 0.993583 [0]
float mul/div: 1.821565 [0]
double add/sub: 0.993884 [0]
double mul/div: 1.988664 [0]

Prosesor AMD Opteron (tm) Dual Core 32-bit 265 @ 1.81GHz, gcc 3.4.6 -O3

short add/sub: 0.553863 [0]
short mul/div: 12.509163 [0]
long add/sub: 0.556912 [0]
long mul/div: 12.748019 [0]
long long add/sub: 5.298999 [0]
long long mul/div: 20.461186 [0]
float add/sub: 2.688253 [0]
float mul/div: 4.683886 [0]
double add/sub: 2.700834 [0]
double mul/div: 4.646755 [0]

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:

#include <stdio.h>
#ifdef _WIN32
#include <sys/timeb.h>
#else
#include <sys/time.h>
#endif
#include <time.h>
#include <cstdlib>

double
mygettime(void) {
# ifdef _WIN32
  struct _timeb tb;
  _ftime(&tb);
  return (double)tb.time + (0.001 * (double)tb.millitm);
# else
  struct timeval tv;
  if(gettimeofday(&tv, 0) < 0) {
    perror("oops");
  }
  return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
# endif
}

template< typename Type >
void my_test(const char* name) {
  Type v  = 0;
  // Do not use constants or repeating values
  //  to avoid loop unroll optimizations.
  // All values >0 to avoid division by 0
  // Perform ten ops/iteration to reduce
  //  impact of ++i below on measurements
  Type v0 = (Type)(rand() % 256)/16 + 1;
  Type v1 = (Type)(rand() % 256)/16 + 1;
  Type v2 = (Type)(rand() % 256)/16 + 1;
  Type v3 = (Type)(rand() % 256)/16 + 1;
  Type v4 = (Type)(rand() % 256)/16 + 1;
  Type v5 = (Type)(rand() % 256)/16 + 1;
  Type v6 = (Type)(rand() % 256)/16 + 1;
  Type v7 = (Type)(rand() % 256)/16 + 1;
  Type v8 = (Type)(rand() % 256)/16 + 1;
  Type v9 = (Type)(rand() % 256)/16 + 1;

  double t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v += v0;
    v -= v1;
    v += v2;
    v -= v3;
    v += v4;
    v -= v5;
    v += v6;
    v -= v7;
    v += v8;
    v -= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s add/sub: %f [%d]\n", name, mygettime() - t1, (int)v&1);
  t1 = mygettime();
  for (size_t i = 0; i < 100000000; ++i) {
    v /= v0;
    v *= v1;
    v /= v2;
    v *= v3;
    v /= v4;
    v *= v5;
    v /= v6;
    v *= v7;
    v /= v8;
    v *= v9;
  }
  // Pretend we make use of v so compiler doesn't optimize out
  //  the loop completely
  printf("%s mul/div: %f [%d]\n", name, mygettime() - t1, (int)v&1);
}

int main() {
  my_test< short >("short");
  my_test< long >("long");
  my_test< long long >("long long");
  my_test< float >("float");
  my_test< double >("double");

  return 0;
}
vladr
sumber
8
mengapa Anda mencampur mult dan div? Bukankah menarik jika mult mungkin (atau diharapkan?) Lebih cepat dari div?
Kyss Tao
13
Perkalian jauh lebih cepat daripada pembagian dalam kasus integer dan floating point. Kinerja divisi tergantung juga pada ukuran angka. Saya biasanya berasumsi bahwa pembagian ~ 15 kali lebih lambat.
Sogartar
4
pastebin.com/Kx8WGUfg Saya mengambil benchmark Anda dan memisahkan setiap operasi ke loopnya sendiri dan menambahkan volatileuntuk memastikan. Di Win64, FPU tidak digunakan dan MSVC tidak akan menghasilkan kode untuk itu, jadi ia mengkompilasi menggunakan mulssdan divssinstruksi XMM di sana, yang 25x lebih cepat daripada FPU di Win32. Mesin uji adalah Core i5 M 520 @ 2.40GHz
James Dunne
4
@JamesDunne berhati-hatilah, karena ops fp vakan dengan cepat mencapai 0 atau +/- inf dengan sangat cepat, yang mungkin (secara teoritis) diperlakukan sebagai kasus khusus / fastpatheed oleh implementasi fpu tertentu.
vladr
3
"Tolok ukur" ini tidak memiliki paralelisme data untuk eksekusi tidak berurutan, karena setiap operasi dilakukan dengan akumulator ( v) yang sama. Pada desain Intel baru-baru ini, pembagian sama sekali tidak dipipeline ( divss/ divpsmemiliki latensi siklus 10-14, dan throughput timbal balik yang sama). mulssbagaimanapun 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).
Peter Cordes
23

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.

Ben Voigt
sumber
4
+1 untuk menunjukkan bagaimana tolok ukur naif dapat menghasilkan loop 0-kali karena operasi integer konstan yang tidak digulung. Selain itu, kompilator dapat sepenuhnya membuang loop (integer atau FP) jika hasilnya tidak benar-benar digunakan.
vladr
Kesimpulannya adalah: seseorang harus memanggil fungsi yang memiliki variabel perulangan sebagai argumen. Karena saya pikir tidak ada kompiler yang dapat melihat bahwa fungsi tersebut tidak melakukan apa-apa dan panggilan tersebut dapat diabaikan. Karena ada panggilan overhead, hanya perbedaan waktu == (waktu float - waktu integer) yang akan signifikan.
GameAlchemist
@GameAlchemist: Banyak kompiler menghilangkan panggilan ke fungsi kosong, sebagai efek samping dari penyebarisan. Anda harus berusaha untuk mencegahnya.
Ben Voigt
OP terdengar seperti dia berbicara tentang menggunakan integer untuk hal-hal di mana FP akan lebih cocok secara alami, jadi akan membutuhkan lebih banyak kode integer untuk mencapai hasil yang sama dengan kode FP. Dalam kasus ini, gunakan saja FP. Misalnya, pada perangkat keras dengan FPU (misalnya CPU desktop), dekoder MP3 bilangan bulat titik tetap lebih lambat (dan kesalahan pembulatan sedikit lebih banyak) daripada dekoder titik mengambang. Implementasi titik tetap dari codec terutama ada untuk dijalankan pada CPU ARM yang dilucuti tanpa perangkat keras FP, hanya FP yang diemulasi lambat.
Peter Cordes
satu contoh untuk poin pertama: pada x86-64 dengan AVX-512 hanya ada 16 register GP tetapi 32 register zmm sehingga matematika floating-point skalar mungkin lebih cepat
phuclv
18

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.

Potatoswatter
sumber
Ya, serta konversi dari rand integer ke float dalam versi floating point. Ada ide tentang cara yang lebih baik untuk menguji ini?
maxpenguin
1
Jika Anda mencoba membuat profil kecepatan, lihat POSIX timespec_tatau yang serupa. Catat waktu di awal dan akhir putaran dan ambil perbedaannya. Kemudian pindahkan pembuatan randdata 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.
Mike D.
3
@maxpenguin: pertanyaannya adalah apa yang Anda uji. Artem berasumsi Anda melakukan grafik, Carl mempertimbangkan apakah Anda menggunakan platform tertanam tanpa FP, saya kira Anda sedang mengkodekan sains untuk server. Anda tidak dapat menggeneralisasi atau "menulis" tolok ukur. Tolok ukur diambil sampelnya dari pekerjaan aktual yang dilakukan program Anda. Satu hal yang dapat saya katakan kepada Anda adalah bahwa itu tidak akan tetap "pada dasarnya kecepatan yang sama" jika Anda menyentuh elemen kinerja-kritis dalam program Anda, apapun itu.
Potatoswatter
poin bagus dan jawaban bagus. Kami memiliki basis kode yang luas saat ini. 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 bantuan Anda.
maxpenguin
18

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

short add: 0.822491
short sub: 0.832757
short mul: 1.007533
short div: 3.459642
long add: 0.824088
long sub: 0.867495
long mul: 1.017164
long div: 5.662498
long long add: 0.873705
long long sub: 0.873177
long long mul: 1.019648
long long div: 5.657374
float add: 1.137084
float sub: 1.140690
float mul: 1.410767
float div: 2.093982
double add: 1.139156
double sub: 1.146221
double mul: 1.405541
double div: 2.093173

Intel i3 2370M memiliki hasil yang serupa

short add: 1.369983
short sub: 1.235122
short mul: 1.345993
short div: 4.198790
long add: 1.224552
long sub: 1.223314
long mul: 1.346309
long div: 7.275912
long long add: 1.235526
long long sub: 1.223865
long long mul: 1.346409
long long div: 7.271491
float add: 1.507352
float sub: 1.506573
float mul: 2.006751
float div: 2.762262
double add: 1.507561
double sub: 1.506817
double mul: 1.843164
double div: 2.877484

Intel (R) Celeron (R) 2955U (Acer C720 Chromebook menjalankan xenial)

short add: 1.999639
short sub: 1.919501
short mul: 2.292759
short div: 7.801453
long add: 1.987842
long sub: 1.933746
long mul: 2.292715
long div: 12.797286
long long add: 1.920429
long long sub: 1.987339
long long mul: 2.292952
long long div: 12.795385
float add: 2.580141
float sub: 2.579344
float mul: 3.152459
float div: 4.716983
double add: 2.579279
double sub: 2.579290
double mul: 3.152649
double div: 4.691226

DigitalOcean 1GB Droplet Intel (R) Xeon (R) CPU E5-2630L v2 (berjalan terpercaya)

short add: 1.094323
short sub: 1.095886
short mul: 1.356369
short div: 4.256722
long add: 1.111328
long sub: 1.079420
long mul: 1.356105
long div: 7.422517
long long add: 1.057854
long long sub: 1.099414
long long mul: 1.368913
long long div: 7.424180
float add: 1.516550
float sub: 1.544005
float mul: 1.879592
float div: 2.798318
double add: 1.534624
double sub: 1.533405
double mul: 1.866442
double div: 2.777649

Prosesor AMD Opteron (tm) 4122 (presisi)

short add: 3.396932
short sub: 3.530665
short mul: 3.524118
short div: 15.226630
long add: 3.522978
long sub: 3.439746
long mul: 5.051004
long div: 15.125845
long long add: 4.008773
long long sub: 4.138124
long long mul: 5.090263
long long div: 14.769520
float add: 6.357209
float sub: 6.393084
float mul: 6.303037
float div: 17.541792
double add: 6.415921
double sub: 6.342832
double mul: 6.321899
double div: 15.362536

Ini menggunakan kode dari http://pastebin.com/Kx8WGUfg sebagaibenchmark-pc.c

g++ -fpermissive -O3 -o benchmark-pc 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):

Bagan data di atas

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)
    short add: 0.773049
    short sub: 0.789793
    short mul: 0.960152
    short div: 3.273668
      int add: 0.837695
      int sub: 0.804066
      int mul: 0.960840
      int div: 3.281113
     long add: 0.829946
     long sub: 0.829168
     long mul: 0.960717
     long div: 5.363420
long long add: 0.828654
long long sub: 0.805897
long long mul: 0.964164
long long div: 5.359342
    float add: 1.081649
    float sub: 1.080351
    float mul: 1.323401
    float div: 1.984582
   double add: 1.081079
   double sub: 1.082572
   double mul: 1.323857
   double div: 1.968488
Prosesor AMD Opteron (tm) 4122 (presisi, hosting bersama DreamHost)
    short add: 1.235603
    short sub: 1.235017
    short mul: 1.280661
    short div: 5.535520
      int add: 1.233110
      int sub: 1.232561
      int mul: 1.280593
      int div: 5.350998
     long add: 1.281022
     long sub: 1.251045
     long mul: 1.834241
     long div: 5.350325
long long add: 1.279738
long long sub: 1.249189
long long mul: 1.841852
long long div: 5.351960
    float add: 2.307852
    float sub: 2.305122
    float mul: 2.298346
    float div: 4.833562
   double add: 2.305454
   double sub: 2.307195
   double mul: 2.302797
   double div: 5.485736
Intel Xeon E5-2630L v2 @ 2.4GHz (64-bit terpercaya, DigitalOcean VPS)
    short add: 1.040745
    short sub: 0.998255
    short mul: 1.240751
    short div: 3.900671
      int add: 1.054430
      int sub: 1.000328
      int mul: 1.250496
      int div: 3.904415
     long add: 0.995786
     long sub: 1.021743
     long mul: 1.335557
     long div: 7.693886
long long add: 1.139643
long long sub: 1.103039
long long mul: 1.409939
long long div: 7.652080
    float add: 1.572640
    float sub: 1.532714
    float mul: 1.864489
    float div: 2.825330
   double add: 1.535827
   double sub: 1.535055
   double mul: 1.881584
   double div: 2.777245
MrMesees
sumber
gcc5 mungkin melakukan vektorisasi otomatis pada sesuatu yang tidak dilakukan oleh gcc4.6? Apakah benchmark-pcmengukur 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).
Peter Cordes
Dan BTW, mengapa tidak menguji int, hanya shortdan long? Di Linux x86-64, shortadalah 16 bit (dan karenanya memiliki penurunan register parsial dalam beberapa kasus), sedangkan longdan long longkeduanya merupakan tipe 64-bit. (Mungkin dirancang untuk Windows di mana x86-64 masih menggunakan 32-bit long? Atau mungkin dirancang untuk mode 32-bit.) Di Linux, x32 ABI memiliki 32-bit longdalam mode 64-bit , jadi jika Anda menginstal pustaka , gunakan gcc -mx32untuk menyusun untuk ILP32. Atau gunakan saja -m32dan lihat longangkanya.
Peter Cordes
Dan Anda harus benar-benar memeriksa apakah kompiler Anda melakukan vektorisasi otomatis. misalnya menggunakan addpsregister xmm sebagai ganti addss, untuk melakukan 4 FP menambahkan secara paralel dalam satu instruksi yang secepat skalar addss. (Gunakan -march=nativeuntuk mengizinkan penggunaan set instruksi apa pun yang didukung CPU Anda, bukan hanya baseline SSE2 untuk x86-64).
Peter Cordes
@cincodenada tolong tinggalkan grafik yang menampilkan 15 penuh ke atas karena itu menggambarkan kinerja.
MrMesees
@PeterCordes Saya akan mencoba untuk melihat besok, terima kasih atas ketekunan Anda.
MrMesees
7

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.

jcoder.dll
sumber
Perlu dicatat bahwa pada Win64, instruksi FPU tidak lagi dibuat oleh kompiler MSVC. Titik mengambang selalu menggunakan petunjuk SIMD di sana. Hal ini membuat perbedaan kecepatan yang besar antara Win32 dan Win64 tentang flop.
James Dunne
5

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.

Goran D
sumber
4

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

Artem Sokolov
sumber
2
Aplikasi ilmiah menggunakan FP. Satu-satunya keuntungan FP adalah presisi tidak berubah-ubah. Ini seperti notasi ilmiah. Jika Anda sudah mengetahui skala angka (misalnya, panjang garis adalah sejumlah piksel), FP dihilangkan. Tetapi sebelum Anda menggambar garis, itu tidak benar.
Potatoswatter
4

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.

gnasher729
sumber
Ada banyak spekulasi liar di sini, tidak memperhitungkan efek sekunder apa pun yang ada di perangkat keras, yang sering mendominasi waktu komputasi. Bukan titik awal yang buruk, tetapi perlu diperiksa pada setiap aplikasi tertentu melalui pembuatan profil, dan tidak diajarkan sebagai Injil.
Ben Voigt
3

Saya menjalankan tes yang baru saja menambahkan 1 ke angka, bukan rand (). Hasil (pada x86-64) adalah:

  • pendek: 4.260s
  • int: 4.020s
  • long long: 3.350s
  • mengapung: 7.330s
  • ganda: 7.210s
dan04
sumber
1
Sumber, opsi kompilasi, dan metode waktu? Saya sedikit terkejut dengan hasilnya.
GManNickG
Loop yang sama seperti OP dengan "rand ()% 365" diganti dengan "1". Tidak ada pengoptimalan. Waktu pengguna dari perintah "waktu".
dan04
13
"Tidak ada pengoptimalan" adalah kuncinya. Anda tidak pernah membuat profil dengan pengoptimalan dinonaktifkan, selalu profil dalam mode "rilis".
Dean Harding
2
Dalam kasus ini, pengoptimalan mati memaksa op terjadi, dan dilakukan dengan sengaja - loop ada untuk melebarkan waktu ke skala pengukuran yang wajar. Menggunakan konstanta 1 menghilangkan biaya rand (). Kompiler pengoptimalan yang cukup cerdas akan melihat 1 ditambahkan 100.000.000 kali tanpa jalan keluar dan cukup menambahkan 100000000 dalam satu operasi. Semacam itu menyiasati seluruh tujuan, bukan?
Stan Rogers
7
@Stan, buat variabel mudah menguap. Bahkan kompilator pengoptimalan yang cerdas pun harus menghormati beberapa operasi itu.
vladr
0

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.

James Curran
sumber
1
Silakan pertimbangkan untuk melihat ini lagi menawarkan lebih dari sekedar opini (terutama mengingat bahwa pendapat tampaknya terbang di hadapan fakta yang dikumpulkan)
MrMesees
1
@MrMesees Meskipun jawaban ini tidak terlalu berguna, menurut saya jawaban ini konsisten dengan tes yang Anda lakukan. Dan hal-hal sepele historis mungkin juga baik-baik saja.
Jonatan Ostrom
Sebagai seseorang yang bekerja dengan 286 pada hari itu, saya dapat mengonfirmasi; "Iya!"
David H Parry