Apa praktik terbaik untuk menggunakan switch
pernyataan vs menggunakan if
pernyataan untuk 30 unsigned
penghitungan di mana sekitar 10 memiliki tindakan yang diharapkan (yang saat ini adalah tindakan yang sama). Kinerja dan ruang perlu dipertimbangkan tetapi tidak kritis. Saya telah mengambil cuplikannya jadi jangan membenci saya karena konvensi penamaan.
switch
pernyataan:
// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing
switch (numError)
{
case ERROR_01 : // intentional fall-through
case ERROR_07 : // intentional fall-through
case ERROR_0A : // intentional fall-through
case ERROR_10 : // intentional fall-through
case ERROR_15 : // intentional fall-through
case ERROR_16 : // intentional fall-through
case ERROR_20 :
{
fire_special_event();
}
break;
default:
{
// error codes that require no additional action
}
break;
}
if
pernyataan:
if ((ERROR_01 == numError) ||
(ERROR_07 == numError) ||
(ERROR_0A == numError) ||
(ERROR_10 == numError) ||
(ERROR_15 == numError) ||
(ERROR_16 == numError) ||
(ERROR_20 == numError))
{
fire_special_event();
}
c++
if-statement
optimization
switch-statement
Semangat-
sumber
sumber
Jawaban:
Gunakan sakelar.
Dalam kasus terburuk kompiler akan menghasilkan kode yang sama dengan rantai if-else, sehingga Anda tidak kehilangan apa pun. Jika ragu, masukkan kasus yang paling umum terlebih dahulu ke pernyataan switch.
Dalam kasus terbaik pengoptimal mungkin menemukan cara yang lebih baik untuk menghasilkan kode. Hal-hal umum yang dilakukan oleh kompiler adalah membangun pohon keputusan biner (menyimpan perbandingan dan melompat dalam kasus rata-rata) atau cukup membangun tabel melompat (berfungsi tanpa perbandingan sama sekali).
sumber
if
-else
rantai dalam template meta pemrograman, dan kesulitan menghasilkanswitch
case
rantai, pemetaan yang mungkin menjadi lebih penting. (dan ya, komentar kuno, tetapi web itu selamanya, atau setidaknya sampai selasa berikutnya)Untuk kasus khusus yang Anda berikan dalam contoh Anda, kode yang paling jelas mungkin:
Jelas ini hanya memindahkan masalah ke area kode yang berbeda, tetapi sekarang Anda memiliki kesempatan untuk menggunakan kembali tes ini. Anda juga memiliki lebih banyak opsi untuk menyelesaikannya. Anda dapat menggunakan std :: set, misalnya:
Saya tidak menyarankan bahwa ini adalah implementasi terbaik dari MemerlukanSpecialEvent, hanya saja itu pilihan. Anda masih bisa menggunakan switch atau rantai if-else, atau tabel pencarian, atau manipulasi bit pada nilainya, apa pun. Semakin tidak jelas proses keputusan Anda, semakin banyak nilai yang akan Anda dapatkan dari memilikinya dalam fungsi yang terisolasi.
sumber
const std::bitset<MAXERR> specialerror(initializer);
Gunakan denganif (specialerror[numError]) { fire_special_event(); }
. Jika Anda ingin memeriksa batas,bitset::test(size_t)
akan memberikan pengecualian pada nilai di luar batas. (bitset::operator[]
tidak rentang-periksa). cplusplus.com/reference/bitset/bitset/test . Ini mungkin akan mengungguli implementasi tabel lompatan yang dihasilkan compilerswitch
, esp. dalam kasus tidak-khusus di mana ini akan menjadi cabang tunggal yang tidak diambil.std::set
, saya pikir saya akan menunjukkan bahwa itu mungkin pilihan yang buruk. Ternyata gcc sudah mengkompilasi kode OP untuk menguji bitmap dalam 32bit langsung. godbolt: goo.gl/qjjv0e . gcc 5.2 bahkan akan melakukan ini untukif
versi. Juga, gcc yang lebih baru akan menggunakan instruksi uji bitbt
alih-alih bergeser untuk meletakkan1
sedikit di tempat yang tepat dan menggunakantest reg, imm32
.Switch adalah lebih cepat.
Coba saja jika ada 30 nilai berbeda di dalam satu loop, dan bandingkan dengan kode yang sama menggunakan sakelar untuk melihat seberapa cepat sakelar itu.
Sekarang, switch memiliki satu masalah nyata : Switch harus tahu pada waktu kompilasi nilai-nilai di dalam setiap case. Ini berarti kode berikut:
tidak akan dikompilasi.
Kebanyakan orang kemudian akan menggunakan mendefinisikan (Aargh!), Dan yang lain akan mendeklarasikan dan mendefinisikan variabel konstan dalam unit kompilasi yang sama. Sebagai contoh:
Jadi, pada akhirnya, pengembang harus memilih antara "speed + clarity" vs. "code coupling".
(Bukan berarti sakelar tidak bisa ditulis untuk membingungkan sekali ... Kebanyakan sakelar yang saya lihat saat ini termasuk kategori "membingungkan" "... Tapi ini adalah cerita lain ...)
.
sumber
Kompiler akan tetap mengoptimalkannya - gunakan sakelar karena itu yang paling mudah dibaca.
sumber
gcc
tidak akan melakukan itu dengan pasti (ada alasan bagus untuk itu). Dentang akan mengoptimalkan kedua kasus menjadi pencarian biner. Sebagai contoh, lihat ini .Switch, jika hanya untuk keterbacaan. Raksasa jika pernyataan lebih sulit dipertahankan dan sulit dibaca menurut saya.
ERROR_01 : // fall-through yang disengaja
atau
(ERROR_01 == numError) ||
Yang belakangan lebih rentan kesalahan dan membutuhkan lebih banyak pengetikan dan pemformatan daripada yang pertama.
sumber
Kode untuk keterbacaan. Jika Anda ingin tahu apa yang berkinerja lebih baik, gunakan profiler, karena pengoptimalan dan penyusun berbeda-beda, dan masalah kinerja jarang terjadi di mana orang berpikir seperti itu.
sumber
Gunakan switch, ini untuk apa dan apa yang diharapkan oleh programmer.
Saya akan memasukkan label case yang berlebihan - hanya untuk membuat orang merasa nyaman, saya mencoba mengingat kapan / apa aturannya untuk tidak menggunakannya.
Anda tidak ingin programmer berikutnya yang mengerjakannya harus melakukan pemikiran yang tidak perlu tentang perincian bahasa (mungkin Anda dalam waktu beberapa bulan!)
sumber
Compiler sangat bagus dalam mengoptimalkan
switch
. Gcc terbaru juga baik dalam mengoptimalkan banyak kondisi dalamif
.Saya membuat beberapa test case di godbolt .
Ketika
case
nilai dikelompokkan berdekatan, gcc, clang, dan icc semuanya cukup pintar untuk menggunakan bitmap untuk memeriksa apakah suatu nilai adalah salah satu yang istimewa.misalnya gcc 5.2 -O3 mengkompilasi
switch
to (danif
sesuatu yang sangat mirip):Perhatikan bahwa bitmap adalah data langsung, jadi tidak ada potensi data-cache ketinggalan mengaksesnya, atau tabel lompatan.
gcc 4.9.2 -O3 mengkompilasi
switch
ke bitmap, tetapi melakukan1U<<errNumber
dengan mov / shift. Ini mengkompilasiif
versi ke serangkaian cabang.Perhatikan cara mengurangi 1 dari
errNumber
(denganlea
menggabungkan operasi itu dengan gerakan). Itu memungkinkannya menyesuaikan bitmap menjadi 32bit segera, menghindari 64bit-langsungmovabsq
yang membutuhkan lebih banyak byte instruksi.Urutan yang lebih pendek (dalam kode mesin) adalah:
(Kegagalan untuk menggunakan
jc fire_special_event
ada di mana-mana, dan merupakan bug penyusun .)rep ret
digunakan dalam target cabang, dan mengikuti cabang bersyarat, untuk kepentingan AMD K8 dan K10 lama (pra-Bulldozer): Apa arti `rep ret`? . Tanpanya, prediksi cabang tidak akan bekerja dengan baik pada CPU yang usang.bt
(uji bit) dengan register arg cepat. Ini menggabungkan pekerjaan menggeser kiri 1 demierrNumber
bit dan melakukan atest
, tetapi masih 1 siklus latensi dan hanya satu Intel uop. Ini lambat dengan memori arg karena caranya terlalu-semantik CISC: dengan operan memori untuk "bit string", alamat byte yang akan diuji dihitung berdasarkan argumen lain (dibagi dengan 8), dan tidak terbatas pada potongan 1, 2, 4, atau 8 byte yang ditunjukkan oleh operan memori.Dari tabel instruksi Agner Fog, instruksi shift penghitungan variabel lebih lambat dari
bt
pada Intel terbaru (2 uops, bukan 1, dan shift tidak melakukan semua hal lain yang diperlukan).sumber
Keuntungan dari sakelar () dibandingkan jika kasing lain adalah: - 1. Sakelar jauh lebih efisien dibandingkan jika kasing karena setiap kasing tidak tergantung pada kasing sebelumnya tidak seperti jika kasing terpisah harus diperiksa untuk kondisi benar atau salah.
Ketika ada no. dari nilai-nilai untuk ekspresi tunggal, sakelar kasus lebih fleksibel daripada jika lain karena dalam jika lain penilaian kasus didasarkan pada hanya dua nilai yaitu benar atau salah.
Nilai-nilai di saklar ditentukan oleh pengguna sedangkan nilai-nilai di dalam kasus lain didasarkan pada kendala.
Jika terjadi kesalahan, pernyataan dalam sakelar dapat dengan mudah diperiksa dan dikoreksi yang relatif sulit untuk diperiksa jika ada pernyataan if else.
Switch case jauh lebih kompak dan mudah dibaca dan dimengerti.
sumber
IMO ini adalah contoh sempurna dari apa saklar jatuh dibuat untuk.
sumber
Jika kasus Anda cenderung tetap dikelompokkan di masa mendatang - jika lebih dari satu kasus sesuai dengan satu hasil - peralihan mungkin terbukti lebih mudah dibaca dan dirawat.
sumber
Mereka bekerja sama baiknya. Performanya hampir sama diberikan kompiler modern.
Saya lebih suka jika pernyataan daripada pernyataan kasus karena lebih mudah dibaca, dan lebih fleksibel - Anda dapat menambahkan kondisi lain yang tidak didasarkan pada kesetaraan numerik, seperti "|| max <min". Tetapi untuk kasus sederhana yang Anda posting di sini, itu tidak masalah, lakukan saja apa yang paling mudah dibaca oleh Anda.
sumber
saklar pasti disukai. Lebih mudah untuk melihat daftar kasus switch & tahu pasti apa yang dilakukannya daripada membaca kondisi jika panjang.
Duplikasi dalam
if
kondisi sulit di mata. Misalkan salah satu==
ditulis!=
; apakah kamu memperhatikan? Atau jika satu contoh 'numError' ditulis 'nmuError', yang kebetulan dikompilasi?Saya biasanya lebih suka menggunakan polimorfisme alih-alih beralih, tetapi tanpa lebih detail konteksnya, sulit dikatakan.
Adapun kinerja, taruhan terbaik Anda adalah menggunakan profiler untuk mengukur kinerja aplikasi Anda dalam kondisi yang mirip dengan apa yang Anda harapkan di alam liar. Kalau tidak, Anda mungkin mengoptimalkan di tempat yang salah dan dengan cara yang salah.
sumber
Saya setuju dengan kemampuan solusi beralih tetapi IMO Anda membajak sakelar di sini.
Tujuan sakelar adalah memiliki penanganan yang berbeda tergantung pada nilainya.
Jika Anda harus menjelaskan algo Anda dalam pseudo-code, Anda akan menggunakan if karena, secara semantik, begitulah: jika whatever_error melakukan ini ...
Jadi kecuali jika suatu hari Anda bermaksud mengubah kode Anda untuk memiliki kode spesifik untuk setiap kesalahan , Saya akan gunakan jika .
sumber
Saya akan memilih pernyataan if demi kejelasan dan konvensi, meskipun saya yakin beberapa akan tidak setuju. Lagi pula, Anda ingin melakukan sesuatu
if
suatu kondisi yang benar! Berganti dengan satu tindakan sepertinya sedikit ... tidak perlu.sumber
Saya akan mengatakan menggunakan SWITCH. Dengan cara ini Anda hanya perlu menerapkan hasil yang berbeda. Sepuluh kasing identik Anda dapat menggunakan default. Jika satu perubahan yang Anda perlukan adalah menerapkan perubahan secara eksplisit, tidak perlu mengedit default. Jauh lebih mudah untuk menambah atau menghapus kasus dari SWITCH daripada mengedit IF dan ELSEIF.
Mungkin bahkan menguji kondisi Anda (dalam hal ini numerror) terhadap daftar kemungkinan, sebuah array mungkin jadi SWITCH Anda bahkan tidak digunakan kecuali pasti akan ada hasilnya.
sumber
Karena Anda hanya memiliki 30 kode kesalahan, buat sendiri tabel lompatan Anda sendiri, lalu buat sendiri semua pilihan pengoptimalan (lompatan akan selalu tercepat), daripada berharap kompiler akan melakukan hal yang benar. Itu juga membuat kode sangat kecil (terlepas dari deklarasi statis dari tabel lompat). Ini juga memiliki keuntungan sampingan bahwa dengan debugger Anda dapat memodifikasi perilaku saat runtime harus Anda butuhkan, hanya dengan menyodok data tabel secara langsung.
sumber
std::bitset<MAXERR> specialerror;
, laluif (specialerror[err]) { special_handler(); }
. Ini akan lebih cepat daripada tabel lompat, esp. dalam kasus yang tidak diambil.Saya tidak yakin tentang praktik terbaik, tapi saya akan menggunakan sakelar - dan kemudian menjebak secara sengaja melalui 'default'
sumber
Secara estetika saya cenderung menyukai pendekatan ini.
Jadikan data sedikit lebih pintar sehingga kita bisa membuat logikanya sedikit bodoh.
Saya menyadari itu terlihat aneh. Inilah inspirasinya (dari bagaimana saya melakukannya dengan Python):
sumber
break
luarcase
((ya, minor) dosa, tapi tetap saja dosa). :)Mungkin yang pertama dioptimalkan oleh kompiler, yang akan menjelaskan mengapa loop kedua lebih lambat saat meningkatkan jumlah loop.
sumber
if
vsswitch
sebagai kondisi loop-end di Jawa.Ketika datang untuk mengkompilasi program, saya tidak tahu apakah ada perbedaan. Tetapi untuk program itu sendiri dan menjaga kode sesederhana mungkin, saya pribadi pikir itu tergantung pada apa yang ingin Anda lakukan. jika tidak, jika tidak, pernyataan memiliki kelebihan, yang menurut saya adalah:
memungkinkan Anda untuk menguji variabel terhadap rentang tertentu Anda dapat menggunakan fungsi (Perpustakaan Standar atau Pribadi) sebagai persyaratan.
(contoh:
Namun, jika tidak, jika lain pernyataan bisa menjadi rumit dan berantakan (meskipun upaya terbaik Anda) terburu-buru. Pergantian pernyataan cenderung lebih jelas, lebih bersih, dan lebih mudah dibaca; tetapi hanya dapat digunakan untuk menguji terhadap nilai-nilai spesifik (contoh:
Saya lebih suka pernyataan if - else if - else, tapi itu terserah Anda. Jika Anda ingin menggunakan fungsi sebagai kondisi, atau Anda ingin menguji sesuatu terhadap rentang, array, atau vektor dan / atau Anda tidak keberatan berurusan dengan sarang yang rumit, saya akan merekomendasikan menggunakan If else if else blok. Jika Anda ingin menguji terhadap nilai tunggal atau Anda ingin blok yang bersih dan mudah dibaca, saya akan merekomendasikan Anda menggunakan switch () blok kasus.
sumber
Saya bukan orang yang memberitahu Anda tentang kecepatan dan penggunaan memori, tetapi melihat statment switch adalah jauh lebih mudah untuk dipahami daripada pernyataan if besar (terutama 2-3 bulan kemudian)
sumber
Saya tahu yang lama tapi
Memvariasikan jumlah loop berubah banyak:
Sementara jika / selain itu: 5ms Switch: 1ms Max Loops: 100000
Sedangkan jika / selain itu: 5ms Switch: 3ms Max Loops: 1000000
Sementara jika / selain itu: 5ms Switch: 14ms Max Loops: 10000000
Sedangkan jika / selain itu: 5ms Switch: 149ms Max Loops: 100000000
(tambahkan lebih banyak pernyataan jika Anda mau)
sumber
if(max) break
loop berjalan dalam waktu yang konstan terlepas dari jumlah lingkaran? Kedengarannya seperti kompiler JIT cukup pintar untuk mengoptimalkan loop kecounter2=max
. Dan mungkin itu lebih lambat daripada beralih jika panggilan pertama kecurrentTimeMillis
memiliki lebih banyak overhead, karena belum semuanya dikompilasi dengan JIT? Menempatkan loop di urutan lain mungkin akan memberikan hasil yang berbeda.