Mana yang lebih cepat: if (bool) atau if (int)?

95

Nilai mana yang lebih baik untuk digunakan? Boolean true atau Integer 1?

Topik di atas membuat saya melakukan beberapa percobaan dengan booldan intdalam ifkondisi. Jadi hanya karena penasaran saya menulis program ini:

int f(int i) 
{
    if ( i ) return 99;   //if(int)
    else  return -99;
}
int g(bool b)
{
    if ( b ) return 99;   //if(bool)
    else  return -99;
}
int main(){}

g++ intbool.cpp -S menghasilkan kode asm untuk setiap fungsi sebagai berikut:

  • kode asm untuk f(int)

    __Z1fi:
       LFB0:
             pushl  %ebp
       LCFI0:
              movl  %esp, %ebp
       LCFI1:
              cmpl  $0, 8(%ebp)
              je    L2
              movl  $99, %eax
              jmp   L3
       L2:
              movl  $-99, %eax
       L3:
              leave
       LCFI2:
              ret
    
  • kode asm untuk g(bool)

    __Z1gb:
       LFB1:
              pushl %ebp
       LCFI3:
              movl  %esp, %ebp
       LCFI4:
              subl  $4, %esp
       LCFI5:
              movl  8(%ebp), %eax
              movb  %al, -4(%ebp)
              cmpb  $0, -4(%ebp)
              je    L5
              movl  $99, %eax
              jmp   L6
       L5:
              movl  $-99, %eax
       L6:
              leave
       LCFI6:
              ret
    

Anehnya, g(bool)menghasilkan lebih banyak asminstruksi! Apakah ini berarti if(bool)lebih lambat dari if(int)? Dulu saya pikir booldirancang khusus untuk digunakan dalam pernyataan bersyarat seperti if, jadi saya mengharapkan g(bool)untuk menghasilkan lebih sedikit instruksi asm, sehingga membuatnya g(bool)lebih efisien dan cepat.

EDIT:

Saya tidak menggunakan tanda pengoptimalan apa pun untuk sekarang. Tetapi meskipun tidak ada, mengapa itu menghasilkan lebih banyak asm untuk g(bool)adalah pertanyaan yang saya cari jawaban yang masuk akal. Saya juga harus memberi tahu Anda bahwa -O2tanda pengoptimalan menghasilkan persis sama dengan asm. Tapi bukan itu pertanyaannya. Pertanyaannya adalah apa yang saya tanyakan.


Nawaz
sumber
32
Ini juga merupakan pengujian yang tidak adil kecuali Anda membandingkannya dengan pengoptimalan yang wajar diaktifkan.
Daniel Pryden
9
@Daniel: Saya tidak menggunakan flag pengoptimalan apa pun dengan keduanya. Tetapi meskipun tidak ada, mengapa itu menghasilkan lebih banyak asm untuk g(bool)adalah pertanyaan yang saya cari jawaban yang masuk akal.
Nawaz
8
Mengapa Anda bersusah payah membaca asm, tetapi tidak hanya menjalankan program dan menentukan waktu hasilnya ? Jumlah instruktur tidak terlalu banyak berbicara tentang kinerja. Anda perlu memperhitungkan tidak hanya panjang instruksi, tetapi juga dependensi dan jenis instruksi (beberapa di antaranya diterjemahkan menggunakan jalur mikrokode yang lebih lambat, unit eksekusi mana yang mereka butuhkan, apa latensi dan throughput instruksi, apakah itu a branch? A memmory access?
jalf
2
@pengguna tidak diketahui, dan @Malvolio: Jelas sekali; Saya tidak melakukan semua ini untuk kode produksi. Seperti yang sudah saya sebutkan di awal posting saya bahwa "Jadi hanya karena penasaran saya menulis program ini" . Jadi ya, ini murni hipotesis .
Nawaz
3
Itu pertanyaan yang sah. Keduanya setara atau lebih cepat. ASM mungkin diposting dalam upaya untuk membantu atau berpikir keras, jadi daripada menggunakannya sebagai cara untuk menghindari pertanyaan dan mengatakan "cukup tulis kode yang dapat dibaca", jawab saja pertanyaan atau STFU jika Anda tidak tahu atau tidak memiliki sesuatu yang berguna untuk dikatakan;) Kontribusi saya adalah bahwa pertanyaan itu dapat dijawab, dan "cukup tulis kode yang dapat dibaca" tidak lain adalah menghindari pertanyaan.
Triynko

Jawaban:

100

Masuk akal bagiku. Kompilator Anda tampaknya mendefinisikan boolsebagai nilai 8-bit, dan ABI sistem Anda memerlukannya untuk "mempromosikan" argumen integer kecil (<32-bit) ke 32-bit saat mendorongnya ke tumpukan panggilan. Jadi untuk membandingkan a bool, kompilator menghasilkan kode untuk mengisolasi byte paling signifikan dari argumen 32-bit yang diterima g, dan membandingkannya cmpb. Dalam contoh pertama, intargumen menggunakan 32 bit penuh yang didorong ke tumpukan, jadi itu hanya membandingkan keseluruhannya dengan cmpl.

Sherm Pendley
sumber
4
Saya setuju. Ini membantu menjelaskan bahwa saat memilih jenis variabel, Anda memilihnya untuk dua tujuan yang berpotensi bersaing, ruang penyimpanan vs. kinerja komputasi.
Triynko
3
Apakah ini juga berlaku untuk proses 64-bit, yang __int64lebih cepat dari int? Atau CPU menangani integer 32-bit dengan set instruksi 32-bit secara terpisah?
Crend King
1
@CrendKing mungkin ada baiknya mengajukan pertanyaan lain?
Nama Tampilan
81

Menyusun dengan -03memberikan yang berikut untuk saya:

f:

    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

g:

    pushl   %ebp
    movl    %esp, %ebp
    cmpb    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

.. sehingga mengkompilasi dasarnya kode yang sama, kecuali untuk cmplvs cmpb. Artinya, perbedaannya, jika ada, tidak masalah. Dilihat oleh kode yang tidak dioptimalkan tidak adil.


Edit untuk memperjelas maksud saya. Kode yang tidak dioptimalkan adalah untuk debugging sederhana, bukan untuk kecepatan. Membandingkan kecepatan kode yang tidak dioptimalkan tidak masuk akal.

Alexander Gessler
sumber
8
Sejauh saya setuju dengan kesimpulan Anda, saya pikir Anda melewatkan bagian yang menarik. Mengapa ini digunakan cmpluntuk yang satu dan cmpbyang lainnya?
jalf
22
@jalf: Karena a booladalah satu byte dan an intadalah empat. Saya tidak berpikir ada yang lebih istimewa dari itu.
CB Bailey
7
Saya pikir tanggapan lain lebih memperhatikan alasannya: itu karena platform yang dimaksud memperlakukan boolsebagai tipe 8 bit.
Alexander Gessler
9
@Nathan: Tidak. C ++ tidak memiliki tipe data bit. Jenis terkecil adalah char, yang menurut definisi adalah byte, dan merupakan unit beralamat terkecil. boolUkurannya ditentukan oleh implementasi, dan mungkin 1, 4, atau 8, atau apa pun. Namun, penyusun cenderung membuatnya menjadi satu.
GManNickG
6
@ Nathan: Itu rumit juga di Java. Java mengatakan bahwa data yang diwakili oleh boolean adalah nilai satu bit, tetapi cara penyimpanan bit tersebut masih ditentukan oleh implementasi. Komputer pragmatis tidak membahas bit.
GManNickG
26

Ketika saya menyusun ini dengan serangkaian opsi yang waras (khususnya -O3), inilah yang saya dapatkan:

Untuk f():

        .type   _Z1fi, @function
_Z1fi:
.LFB0:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpl    $1, %edi
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc

Untuk g():

        .type   _Z1gb, @function
_Z1gb:
.LFB1:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpb    $1, %dil
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc

Mereka masih menggunakan instruksi yang berbeda untuk perbandingan ( cmpbuntuk boolean vs.cmpl untuk int), tetapi sebaliknya badannya identik. Sekilas melihat manual Intel memberitahu saya: ... tidak banyak apa-apa. Tidak ada yang namanya cmpbatau cmpldi manual Intel. Mereka semua cmpdan saya tidak dapat menemukan tabel waktu saat ini. Saya menebak, bagaimanapun, bahwa tidak ada perbedaan jam antara membandingkan byte langsung vs membandingkan langsung panjang, jadi untuk semua tujuan praktis kodenya identik.


diedit untuk menambahkan yang berikut ini berdasarkan penambahan Anda

Alasan kode berbeda dalam kasus yang tidak dioptimalkan adalah karena kode tidak dioptimalkan. (Ya, ini melingkar, saya tahu.) Ketika kompilator menjalankan AST dan menghasilkan kode secara langsung, ia tidak "tahu" apa pun kecuali apa yang ada di titik langsung AST itu masuk. Pada titik itu ia kekurangan semua informasi kontekstual yang diperlukan untuk mengetahui bahwa pada titik khusus ini, ia dapat memperlakukan tipe yang dideklarasikan boolsebagaiint . Boolean jelas secara default diperlakukan sebagai byte dan ketika memanipulasi byte di dunia Intel Anda harus melakukan hal-hal seperti sign-extends untuk membawanya ke lebar tertentu untuk diletakkan di tumpukan, dll. (Anda tidak dapat mendorong byte .)

Namun, ketika pengoptimal melihat AST dan melakukan keajaibannya, pengoptimal melihat konteks sekitarnya dan "mengetahui" kapan dapat mengganti kode dengan sesuatu yang lebih efisien tanpa mengubah semantik. Jadi "tahu" itu bisa menggunakan bilangan bulat dalam parameter dan dengan demikian kehilangan konversi dan pelebaran yang tidak perlu.

HANYA PENDAPAT SAYA yang benar
sumber
1
Haha, saya suka bagaimana kompilator hanya mengembalikan 99, atau 99 + 58 = 157 = -99 (overflow 8bits yang ditandatangani) ... menarik.
deceleratedcaviar
@ Daniel: Bahkan aku menyukainya. Awalnya, saya berkata "di mana -99" dan segera saya menyadari bahwa itu melakukan sesuatu yang sangat aneh.
Nawaz
7
ldan bsufiks yang digunakan hanya dalam sintaks AT&T. Mereka hanya mengacu pada versi cmpmenggunakan operan 4 byte (panjang) dan 1 byte (byte). Jika ada ambiguitas dalam sintaksis intel, secara konvensional operan memori diberi tag BYTE PTR, WORD PTRatau DWORD PTRalih-alih memberi sufiks pada opcode.
CB Bailey
Tabel waktu: agner.org/optimize Kedua ukuran operan cmpmemiliki kinerja yang sama, dan tidak ada hukuman register parsial untuk membaca %dil . (Tapi itu tidak menghentikan clang dari membuat kios register parsial yang lucu dengan menggunakan ukuran byte andpada AL sebagai bagian dari case-flipping tanpa cabang antara 99 dan -99.)
Peter Cordes
13

Dengan GCC 4.5 di Linux dan Windows setidaknya, sizeof(bool) == 1 . Pada x86 dan x86_64, Anda tidak dapat mengirimkan kurang dari nilai register tujuan umum ke suatu fungsi (baik melalui stack atau register tergantung pada konvensi pemanggilan, dll ...).

Jadi kode untuk bool, ketika tidak dioptimasi, sebenarnya berjalan cukup panjang untuk mengekstrak nilai bool itu dari tumpukan argumen (menggunakan slot tumpukan lain untuk menyimpan byte itu). Ini lebih rumit daripada hanya menarik variabel berukuran register asli.

Tikar
sumber
Dari standar C ++ 03, §5.3.3 / 1: " sizeof(bool)dan sizeof(wchar_t)ditentukan oleh implementasi " . Jadi, ucapan sizeof(bool) == 1tersebut tidak sepenuhnya benar kecuali Anda berbicara tentang versi tertentu dari kompilator tertentu.
ildjarn
9

Pada level mesin tidak ada yang namanya bool

Sangat sedikit arsitektur set instruksi yang mendefinisikan jenis jenis operan boolean apa pun, meskipun sering ada instruksi yang memicu tindakan pada nilai bukan nol. Untuk CPU, biasanya, semuanya adalah salah satu jenis skalar atau rangkaiannya.

Kompiler tertentu dan ABI tertentu perlu memilih ukuran tertentu untuk intdan booljika, seperti dalam kasus Anda, ukuran ini berbeda, mereka mungkin menghasilkan kode yang sedikit berbeda, dan pada beberapa tingkat pengoptimalan, satu mungkin sedikit lebih cepat.

Mengapa bool satu byte pada banyak sistem?

Lebih aman memilih chartipe untuk bool karena seseorang mungkin membuat array yang sangat besar.

Update: dengan "lebih aman", maksud saya: untuk compiler dan pelaksana perpustakaan. Saya tidak mengatakan orang perlu menerapkan ulang tipe sistem.

DigitalRoss
sumber
2
+1 Bayangkan overhead pada x86 jika booldiwakili oleh bit; jadi byte akan menjadi pertukaran yang bagus untuk kecepatan / kekompakan data di banyak implementasi.
Hardmath
1
@ Billy: Saya pikir dia tidak mengatakan "gunakan chardaripada bool" tetapi hanya menggunakan " chartipe" yang berarti "1 byte" ketika mengacu pada ukuran yang dipilih kompilator untuk boolobjek.
Dennis Zickefoose
Oh, tentu, saya tidak bermaksud bahwa setiap program harus memilih, saya hanya mengemukakan alasan mengapa tipe sistem bool adalah 1 byte.
DigitalRoss
@ Dennis: Ah, itu masuk akal.
Billy ONeal
7

Ya, diskusi itu menyenangkan. Tapi coba saja:

Kode tes:

#include <stdio.h>
#include <string.h>

int testi(int);
int testb(bool);
int main (int argc, char* argv[]){
  bool valb;
  int  vali;
  int loops;
  if( argc < 2 ){
    return 2;
  }
  valb = (0 != (strcmp(argv[1], "0")));
  vali = strcmp(argv[1], "0");
  printf("Arg1: %s\n", argv[1]);
  printf("BArg1: %i\n", valb ? 1 : 0);
  printf("IArg1: %i\n", vali);
  for(loops=30000000; loops>0; loops--){
    //printf("%i: %i\n", loops, testb(valb=!valb));
    printf("%i: %i\n", loops, testi(vali=!vali));
  }
  return valb;
}

int testi(int val){
  if( val ){
    return 1;
  }
  return 0;
}
int testb(bool val){
  if( val ){
    return 1;
  }
  return 0;
}

Dikompilasi pada laptop Ubuntu 10.10 64-bit dengan: g ++ -O3 -o / tmp / test_i /tmp/test_i.cpp

Perbandingan berbasis bilangan bulat:

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.203s
user    0m8.170s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.056s
user    0m8.020s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.116s
user    0m8.100s
sys 0m0.000s

Uji Boolean / cetak tanpa komentar (dan bilangan bulat berkomentar):

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.254s
user    0m8.240s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.028s
user    0m8.000s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m7.981s
user    0m7.900s
sys 0m0.050s

Mereka sama dengan 1 tugas dan 2 perbandingan setiap loop lebih dari 30 juta loop. Temukan hal lain untuk dioptimalkan. Misalnya, jangan gunakan strcmp jika tidak perlu. ;)

dannysauer
sumber
0

Mendekati pertanyaan Anda dengan dua cara berbeda:

Jika Anda secara khusus berbicara tentang C ++ atau bahasa pemrograman apa pun yang akan menghasilkan kode assembly dalam hal ini, kami terikat pada kode apa yang akan dihasilkan oleh compiler di ASM. Kami juga terikat dengan representasi benar dan salah di c ++. Integer harus disimpan dalam 32 bit, dan saya cukup menggunakan byte untuk menyimpan ekspresi boolean. Cuplikan ASM untuk pernyataan bersyarat:

Untuk bilangan bulat:

  mov eax,dword ptr[esp]    ;Store integer
  cmp eax,0                 ;Compare to 0
  je  false                 ;If int is 0, its false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

Untuk bool:

  mov  al,1     ;Anything that is not 0 is true
  test al,1     ;See if first bit is fliped
  jz   false    ;Not fliped, so it's false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

Jadi, itulah mengapa perbandingan kecepatan sangat bergantung pada kompilasi. Dalam kasus di atas, bool akan sedikit lebih cepat karena cmpakan mengimplikasikan pengurangan untuk pengaturan flag. Ini juga bertentangan dengan apa yang dihasilkan kompilator Anda.

Pendekatan lain, yang jauh lebih sederhana, adalah dengan melihat logika ekspresi itu sendiri dan mencoba untuk tidak khawatir tentang bagaimana kompilator akan menerjemahkan kode Anda, dan saya pikir ini adalah cara berpikir yang jauh lebih sehat. Saya masih percaya, pada akhirnya, bahwa kode yang dihasilkan oleh kompilator sebenarnya mencoba memberikan resolusi yang benar. Yang saya maksud adalah, mungkin jika Anda meningkatkan kasus uji di pernyataan if dan tetap menggunakan boolean di satu sisi dan bilangan bulat di sisi lain, kompilator akan membuatnya sehingga kode yang dihasilkan akan dieksekusi lebih cepat dengan ekspresi boolean di tingkat mesin.

Saya menganggap ini adalah pertanyaan konseptual, jadi saya akan memberikan jawaban konseptual. Diskusi ini mengingatkan saya pada diskusi yang biasa saya lakukan tentang apakah efisiensi kode diterjemahkan menjadi lebih sedikit baris kode dalam perakitan. Tampaknya konsep ini secara umum diterima sebagai benar. Mempertimbangkan bahwa melacak seberapa cepat ALU akan menangani setiap pernyataan tidak memungkinkan, opsi kedua adalah fokus pada lompatan dan perbandingan dalam perakitan. Jika demikian, perbedaan antara pernyataan boolean atau bilangan bulat dalam kode yang Anda sajikan menjadi agak representatif. Hasil ekspresi dalam C ++ akan mengembalikan nilai yang kemudian akan diberi representasi. Di sisi lain, dalam perakitan, lompatan dan perbandingan akan didasarkan pada nilai numerik terlepas dari jenis ekspresi apa yang dievaluasi kembali pada Anda C ++ if pernyataan. Penting pada pertanyaan-pertanyaan ini untuk mengingat bahwa pernyataan logika murni seperti ini berakhir dengan overhead komputasi yang sangat besar, meskipun satu bit akan mampu melakukan hal yang sama.

Artie
sumber