Mengapa menggunakan pendekatan OO alih-alih pernyataan "beralih" raksasa?

59

Saya bekerja di .Net, C # shop dan saya memiliki rekan kerja yang terus bersikeras bahwa kita harus menggunakan pernyataan Switch raksasa dalam kode kita dengan banyak "Kasus" daripada pendekatan yang lebih berorientasi objek. Argumennya secara konsisten kembali ke fakta bahwa pernyataan Switch mengkompilasi ke "tabel lompatan cpu" dan karena itu merupakan opsi tercepat (meskipun dalam hal lain tim kami diberitahu bahwa kami tidak peduli dengan kecepatan).

Sejujurnya aku tidak punya argumen menentang ini ... karena aku tidak tahu apa yang dia bicarakan.
Apakah dia benar
Apakah dia hanya berbicara di pantatnya?
Hanya berusaha belajar di sini.

James P. Wright
sumber
7
Anda dapat memverifikasi apakah dia benar dengan menggunakan sesuatu seperti .NET Reflector untuk melihat kode perakitan dan mencari "tabel cpu jump".
FrustratedWithFormsDesigner
5
"Alihkan kompilasi pernyataan ke" cpu jump table "Begitu juga metode terburuk yang mengirim semua fungsi murni-virtual. Tidak ada fungsi virtual yang langsung terhubung secara langsung. Apakah Anda membuang kode apa pun untuk dibandingkan?
S.Lott
64
Kode harus ditulis untuk ORANG bukan untuk mesin, kalau tidak kita hanya akan melakukan segalanya dalam perakitan.
maple_shaft
8
Jika dia banyak noodge, mengutip Knuth kepadanya: "Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan."
DaveE
12
Maintabilitas. Ada pertanyaan lain dengan satu kata jawaban yang dapat saya bantu?
Matt Ellen

Jawaban:

48

Dia mungkin seorang peretas C tua dan ya, dia berbicara keluar dari pantatnya. .Net bukan C ++; kompiler .Net terus menjadi lebih baik dan sebagian besar peretas cerdas adalah kontra-produktif, jika tidak hari ini maka dalam versi .Net berikutnya. Fungsi-fungsi kecil lebih disukai karena .Net JIT-s setiap fungsi satu kali sebelum digunakan. Jadi, jika beberapa kasus tidak pernah terkena selama siklus program, jadi tidak ada biaya yang dikeluarkan dalam kompilasi JIT ini. Bagaimanapun, jika kecepatan bukan masalah, seharusnya tidak ada optimasi. Tulis untuk programmer terlebih dahulu, untuk compiler kedua. Rekan kerja Anda tidak akan mudah diyakinkan, jadi saya akan membuktikan secara empiris bahwa kode yang terorganisir lebih baik sebenarnya lebih cepat. Saya akan memilih salah satu contoh terburuknya, menulis ulang dengan cara yang lebih baik, dan kemudian memastikan bahwa kode Anda lebih cepat. Pilih Cherry jika Anda harus. Kemudian jalankan beberapa juta kali, profil dan tunjukkan padanya.

SUNTING

Bill Wagner menulis:

Butir 11: Memahami Ketertarikan Fungsi Kecil (Efektif C # Edisi Kedua) Ingatlah bahwa menerjemahkan kode C # Anda ke dalam kode yang dapat dieksekusi mesin adalah proses dua langkah. Compiler C # menghasilkan IL yang dikirimkan dalam kumpulan. Kompiler JIT menghasilkan kode mesin untuk setiap metode (atau kelompok metode, ketika inlining terlibat), sesuai kebutuhan. Fungsi kecil memudahkan kompiler JIT untuk mengamortisasi biaya itu. Fungsi kecil juga lebih cenderung menjadi kandidat untuk inlining. Ini bukan hanya kekecilan: Aliran kontrol yang lebih sederhana juga sama pentingnya. Lebih sedikit kontrol cabang di dalam fungsi membuatnya lebih mudah untuk kompiler JIT untuk mendaftarkan variabel. Bukan hanya praktik yang baik untuk menulis kode yang lebih jelas; ini adalah cara Anda membuat kode yang lebih efisien saat runtime.

EDIT2:

Jadi ... rupanya pernyataan switch lebih cepat dan lebih baik daripada sekelompok pernyataan if / else, karena satu perbandingan adalah logaritmik dan yang lainnya adalah linear. http://afterence-points.blogspot.com/2007/10/why-is-switch-statement-faster-than-if.html

Yah, pendekatan favorit saya untuk mengganti pernyataan switch besar adalah dengan kamus (atau kadang-kadang bahkan sebuah array jika saya mengaktifkan enum atau int kecil) yang memetakan nilai ke fungsi yang dipanggil untuk menanggapi mereka. Melakukan hal itu memaksa seseorang untuk menghapus banyak keadaan spageti bersama yang jahat, tetapi itu adalah hal yang baik. Pernyataan peralihan besar biasanya merupakan mimpi buruk pemeliharaan. Jadi ... dengan array dan kamus, pencarian akan memakan waktu yang konstan, dan akan ada sedikit memori tambahan yang terbuang.

Saya masih tidak yakin bahwa pergantian pernyataan lebih baik.

Pekerjaan
sumber
47
Jangan khawatir tentang membuktikannya lebih cepat. Ini adalah optimasi prematur. Milidetik yang mungkin Anda simpan tidak seberapa dibandingkan dengan indeks yang Anda lupa tambahkan ke database yang menghabiskan biaya 200 ms. Anda bertempur di pertempuran yang salah.
Rein Henrichs
27
@ Pekerjaan bagaimana jika dia benar? Intinya bukan bahwa dia salah, intinya adalah dia benar dan itu tidak masalah .
Rein Henrichs
2
Bahkan jika dia benar tentang 100% kasus, dia masih menyia-nyiakan waktu kita.
Jeremy
6
Saya ingin mencungkil mata saya mencoba membaca halaman yang Anda tautkan.
AttackingHobo
3
Ada apa dengan C ++ benci? Kompiler C ++ menjadi lebih baik juga, dan switch besar sama buruknya di C ++ seperti di C #, dan untuk alasan yang persis sama. Jika Anda dikelilingi oleh mantan programmer C ++ yang memberi Anda kesedihan itu bukan karena mereka adalah programmer C ++, itu karena mereka adalah programmer yang buruk.
Sebastian Redl
39

Kecuali kolega Anda dapat memberikan bukti, bahwa perubahan ini memberikan manfaat terukur aktual pada skala seluruh aplikasi, itu lebih rendah daripada pendekatan Anda (yaitu polimorfisme), yang sebenarnya memberikan manfaat seperti itu: pemeliharaan.

Optimalisasi mikro hanya boleh dilakukan, setelah kemacetan dijabarkan. Optimalisasi prematur adalah akar dari semua kejahatan .

Kecepatan dapat diukur. Ada sedikit informasi berguna dalam "pendekatan A lebih cepat daripada pendekatan B". Pertanyaannya adalah " Seberapa cepat? ".

back2dos
sumber
2
Sepenuhnya benar. Jangan pernah mengklaim bahwa ada sesuatu yang lebih cepat, selalu ukur. Dan mengukur hanya ketika bagian dari aplikasi itu adalah hambatan kinerja.
Kilian Foth
6
-1 untuk "Optimalisasi prematur adalah akar dari semua kejahatan." Harap tampilkan seluruh kutipan, bukan hanya satu bagian yang bias pendapat Knuth.
alternatif
2
@ mathepic: Saya sengaja tidak menyajikan ini sebagai kutipan. Kalimat ini, sebagaimana adanya, adalah pendapat pribadi saya, meskipun tentu saja bukan ciptaan saya. Meskipun dapat dicatat bahwa orang-orang dari c2 tampaknya menganggap itu hanya bagian dari inti kebijaksanaan.
back2dos
8
@alternative Kutipan Knuth lengkap "Tidak ada keraguan bahwa tingkat efisiensi mengarah pada penyalahgunaan. Programmer menghabiskan banyak waktu untuk memikirkan, atau mengkhawatirkan, kecepatan bagian nonkritis dari program mereka, dan upaya efisiensi ini sebenarnya memiliki dampak negatif yang kuat ketika debugging dan pemeliharaan dipertimbangkan. Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan. " Menjelaskan rekan kerja OP dengan sempurna. IMHO back2dos merangkum kutipan dengan baik dengan "optimasi prematur adalah akar dari semua kejahatan"
MarkJ
2
@ MarkJ 97% dari waktu
alternatif
27

Siapa yang peduli jika lebih cepat?

Kecuali jika Anda menulis perangkat lunak waktu nyata, kecil kemungkinan peningkatan kecepatan yang mungkin Anda dapatkan dari melakukan sesuatu dengan cara yang benar-benar gila akan membuat banyak perbedaan bagi klien Anda. Aku bahkan tidak mau bertempur melawan yang ini di depan, orang ini jelas tidak akan mendengarkan argumen tentang masalah ini.

Namun, kemampuan mempertahankan adalah tujuan dari permainan ini, dan pernyataan peralihan raksasa bahkan tidak dapat dipertahankan, bagaimana Anda menjelaskan jalur yang berbeda melalui kode ke orang baru? Dokumentasi harus sepanjang kode itu sendiri!

Plus, Anda kemudian mendapatkan ketidakmampuan lengkap untuk pengujian unit secara efektif (terlalu banyak jalur yang mungkin, belum lagi kemungkinan kurangnya antarmuka dll.), Yang membuat kode Anda semakin tidak bisa dirawat.

[Di sisi yang tertarik: JITter berkinerja lebih baik pada metode yang lebih kecil, jadi pernyataan peralihan raksasa (dan metode yang pada dasarnya besar) akan membahayakan kecepatan Anda di majelis besar, IIRC.]

Ed James
sumber
1
+ contoh besar optimasi prematur.
ShaneC
Ini pasti.
DeadMG
Memberi +1 untuk 'pernyataan beralih raksasa bahkan tidak bisa dipertahankan'
Korey Hinton
2
Pernyataan peralihan raksasa jauh lebih mudah bagi orang baru untuk memahami: semua perilaku yang mungkin dikumpulkan di sana dalam daftar yang rapi dan bagus. Panggilan tidak langsung sangat sulit diikuti, dalam kasus terburuk (penunjuk fungsi) Anda perlu mencari seluruh basis kode untuk fungsi tanda tangan yang benar, dan panggilan virtual hanya sedikit lebih baik (mencari fungsi dengan nama dan tanda tangan yang tepat dan terkait dengan warisan). Tetapi perawatan bukan tentang menjadi hanya-baca.
Ben Voigt
14

Langkah menjauh dari pernyataan beralih ...

Pernyataan peralihan jenis ini harus dijauhi seperti wabah karena melanggar Prinsip Terbuka Terbuka . Ini memaksa tim untuk membuat perubahan pada kode yang ada ketika fungsionalitas baru perlu ditambahkan, sebagai lawan dari, hanya menambahkan kode baru.

Dakotah Utara
sumber
11
Itu datang dengan peringatan. Ada operasi (fungsi / metode) dan jenis. Ketika Anda menambahkan operasi baru, Anda hanya perlu mengubah kode di satu tempat untuk pernyataan switch (menambahkan satu fungsi baru dengan pernyataan switch), tetapi Anda harus menambahkan metode itu ke semua kelas dalam kasus OO (melanggar open / prinsip tertutup). Jika Anda menambahkan tipe baru, Anda harus menyentuh setiap pernyataan switch, tetapi dalam kasus OO Anda hanya akan menambahkan satu kelas lagi. Oleh karena itu untuk membuat keputusan yang tepat Anda harus tahu apakah Anda akan menambahkan lebih banyak operasi ke jenis yang ada, atau menambahkan lebih banyak jenis.
Scott Whitlock
3
Jika Anda perlu menambahkan lebih banyak operasi ke tipe yang ada dalam paradigma OO tanpa melanggar OCP, maka saya percaya itulah gunanya pola pengunjung.
Scott Whitlock
3
@ Martin - panggil nama jika Anda mau, tetapi ini adalah tradeoff yang terkenal. Saya merujuk Anda ke Clean Code oleh RC Martin. Dia meninjau kembali artikelnya di OCP, menjelaskan apa yang saya uraikan di atas. Anda tidak dapat secara bersamaan mendesain untuk semua persyaratan di masa mendatang. Anda harus membuat pilihan antara apakah itu lebih cenderung menambah lebih banyak operasi atau lebih banyak jenis. OO mendukung penambahan tipe. Anda dapat menggunakan OO untuk menambahkan lebih banyak operasi jika Anda memodelkan operasi sebagai kelas, tetapi itu tampaknya masuk ke dalam pola pengunjung, yang memiliki masalah sendiri (terutama overhead).
Scott Whitlock
8
@ Martin: Apakah Anda pernah menulis parser? Cukup umum untuk memiliki sakelar kasus besar yang mengaktifkan token berikutnya di buffer lookahead. Anda bisa mengganti sakelar-sakelar tersebut dengan pemanggilan fungsi virtual ke token berikutnya, tetapi itu akan menjadi mimpi buruk pemeliharaan. Jarang, tetapi kadang-kadang switch-case sebenarnya adalah pilihan yang lebih baik, karena itu membuat kode yang harus dibaca / dimodifikasi bersama dalam jarak dekat.
nikie
1
@ Martin: Anda menggunakan kata-kata seperti "tidak pernah", "pernah" dan "Poppycock", jadi saya berasumsi Anda berbicara tentang semua kasus tanpa pengecualian, bukan hanya tentang kasus yang paling umum. (Dan BTW: Orang masih menulis parser dengan tangan. Misalnya, parser CPython masih ditulis dengan tangan, IIRC.)
nikie
8

Saya selamat dari mimpi buruk yang dikenal sebagai mesin negara berhingga besar yang dimanipulasi oleh pernyataan peralihan besar-besaran. Lebih buruk lagi, dalam kasus saya, FSM membentang tiga C ++ DLL dan itu cukup jelas kode itu ditulis oleh seseorang yang berpengalaman dalam C.

Metrik yang perlu Anda perhatikan adalah:

  • Kecepatan membuat perubahan
  • Kecepatan menemukan masalah saat itu terjadi

Saya diberi tugas untuk menambahkan fitur baru ke set DLL itu, dan mampu meyakinkan manajemen bahwa saya akan membutuhkan waktu lama untuk menulis ulang 3 DLL sebagai satu DLL yang berorientasi objek dengan benar sebagaimana bagi saya untuk menambal monyet dan juri rig solusi ke dalam apa yang sudah ada di sana. Penulisan ulang itu sukses besar, karena tidak hanya mendukung fungsi baru tetapi lebih mudah untuk diperluas. Bahkan, tugas yang biasanya akan memakan waktu seminggu untuk memastikan Anda tidak memecahkan apa pun akan berakhir dengan memakan waktu beberapa jam.

Lantas bagaimana dengan waktu eksekusi? Tidak ada peningkatan atau penurunan kecepatan. Agar adil, kinerja kami dibatasi oleh driver sistem, jadi jika solusi berorientasi objek sebenarnya lebih lambat, kami tidak akan mengetahuinya.

Apa yang salah dengan pernyataan peralihan besar-besaran untuk bahasa OO?

  • Aliran kontrol program diambil dari objek di mana ia berada dan ditempatkan di luar objek
  • Banyak titik kontrol eksternal diterjemahkan ke banyak tempat yang perlu Anda tinjau
  • Tidak jelas di mana status disimpan, terutama jika sakelar berada di dalam sebuah loop
  • Perbandingan tercepat adalah tidak ada perbandingan sama sekali (Anda dapat menghindari kebutuhan untuk banyak perbandingan dengan desain berorientasi objek yang baik)
  • Lebih efisien untuk mengulangi melalui objek Anda dan selalu memanggil metode yang sama pada semua objek daripada mengubah kode Anda berdasarkan jenis objek atau enum yang mengkodekan tipe tersebut.
Berin Loritsch
sumber
8

Saya tidak membeli argumen kinerja; ini semua tentang pemeliharaan kode.

TETAPI: kadang-kadang , pernyataan switch raksasa lebih mudah dipertahankan (lebih sedikit kode) daripada sekelompok kelas kecil yang mengesampingkan fungsi virtual dari kelas dasar abstrak. Misalnya, jika Anda mengimplementasikan emulator CPU, Anda tidak akan mengimplementasikan fungsionalitas dari setiap instruksi dalam kelas yang terpisah - Anda hanya akan memasukkannya ke dalam swtich raksasa pada opcode, mungkin memanggil fungsi pembantu untuk instruksi yang lebih kompleks.

Rule of thumb: jika switch entah bagaimana dilakukan pada TYPE, Anda mungkin harus menggunakan fungsi warisan dan virtual. Jika sakelar dilakukan pada VALUE dari tipe tetap (mis., Opcode instruksi, seperti di atas), tidak apa-apa untuk membiarkannya apa adanya.

zvrba
sumber
5

Anda tidak dapat meyakinkan saya bahwa:

void action1()
{}

void action2()
{}

void action3()
{}

void action4()
{}

void doAction(int action)
{
    switch(action)
    {
        case 1: action1();break;
        case 2: action2();break;
        case 3: action3();break;
        case 4: action4();break;
    }
}

Secara signifikan lebih cepat daripada:

struct IAction
{
    virtual ~IAction() {}
    virtual void action() = 0;
}

struct Action1: public IAction
{
    virtual void action()    { }
}

struct Action2: public IAction
{
    virtual void action()    { }
}

struct Action3: public IAction
{
    virtual void action()    { }
}

struct Action4: public IAction
{
    virtual void action()    { }
}

void doAction(IAction& actionObject)
{
    actionObject.action();
}

Selain itu versi OO lebih mudah dikelola.

Martin York
sumber
8
Untuk beberapa hal dan untuk tindakan dalam jumlah yang lebih kecil, versi OO jauh lebih konyol. Itu harus memiliki semacam pabrik untuk mengubah beberapa nilai menjadi penciptaan IAction. Dalam banyak kasus, lebih mudah dibaca untuk hanya mengaktifkan nilai itu saja.
Zan Lynx
@ Zan Lynx: Argumen Anda terlalu umum. Pembuatan objek IAction sama sulitnya dengan mengambil tindakan integer tidak lebih sulit dan tidak lebih mudah. Jadi kita bisa melakukan percakapan nyata tanpa menjadi cara generik. Pertimbangkan kalkulator. Apa perbedaan kompleksitas di sini? Jawabannya nol. Karena semua tindakan sudah dibuat sebelumnya. Anda mendapatkan input dari pengguna dan sudah menjadi tindakan.
Martin York
3
@ Martin: Anda mengasumsikan aplikasi kalkulator GUI. Mari kita ambil aplikasi kalkulator keyboard yang ditulis untuk C ++ pada sistem tertanam. Sekarang Anda memiliki integer kode pindai dari register perangkat keras. Sekarang apa yang kurang kompleks?
Zan Lynx
2
@ Martin: Anda tidak melihat bagaimana integer -> lookup table -> pembuatan objek baru -> panggilan fungsi virtual lebih rumit daripada integer -> switch -> fungsi? Bagaimana kamu tidak melihatnya?
Zan Lynx
2
@ Martin: Mungkin saya akan. Sementara itu, jelaskan bagaimana Anda mendapatkan objek IAction untuk memanggil action () aktif dari integer tanpa tabel pencarian.
Zan Lynx
4

Dia benar bahwa kode mesin yang dihasilkan mungkin akan lebih efisien. Essential compiler mengubah pernyataan switch menjadi serangkaian tes dan cabang, yang akan relatif sedikit instruksi. Ada kemungkinan besar bahwa kode yang dihasilkan dari pendekatan yang lebih abstrak akan membutuhkan lebih banyak instruksi.

NAMUN : Hampir pasti bahwa aplikasi khusus Anda tidak perlu khawatir tentang optimasi mikro semacam ini, atau Anda tidak akan menggunakan .net di tempat pertama. Untuk apa pun yang kurang dari aplikasi tertanam yang sangat terbatas, atau pekerjaan intensif CPU Anda harus selalu membiarkan kompiler menangani optimasi. Berkonsentrasi pada penulisan kode yang bersih dan dapat dipelihara. Ini hampir selalu bernilai jauh lebih besar daripada beberapa persepuluh nano-detik dalam waktu eksekusi.

Luke Graham
sumber
3

Salah satu alasan utama untuk menggunakan kelas alih-alih beralih pernyataan adalah bahwa pernyataan beralih cenderung mengarah ke satu file besar yang memiliki banyak logika. Ini adalah mimpi buruk pemeliharaan serta masalah dengan manajemen sumber karena Anda harus memeriksa dan mengedit file besar itu daripada file kelas kecil yang berbeda

Homde
sumber
3

pernyataan switch dalam kode OOP adalah indikasi kuat dari kelas yang hilang

cobalah keduanya dan jalankan beberapa tes kecepatan sederhana; kemungkinan perbedaannya tidak signifikan. Jika ya dan kode itu penting untuk waktu maka pertahankan pernyataan peralihan

Steven A. Lowe
sumber
3

Biasanya saya benci kata, "optimasi prematur", tetapi ini berbau itu. Perlu dicatat bahwa Knuth menggunakan kutipan terkenal ini dalam konteks mendorong untuk menggunakan gotopernyataan untuk mempercepat kode di area kritis . Itulah kuncinya: jalur kritis .

Dia menyarankan untuk digunakan gotountuk mempercepat kode tetapi memperingatkan terhadap para programmer yang ingin melakukan hal-hal semacam ini berdasarkan firasat dan takhayul untuk kode yang bahkan tidak kritis.

Untuk mendukung switchpernyataan sebanyak mungkin secara seragam di seluruh basis kode (terlepas dari apakah ada beban berat yang ditangani) adalah contoh klasik dari apa yang Knuth sebut sebagai programmer "penny-wise and pound-bodoh" yang menghabiskan sepanjang hari berjuang untuk mempertahankan "dioptimalkan" mereka "kode yang berubah menjadi mimpi buruk debugging sebagai hasil dari mencoba untuk menyimpan uang lebih dari pound. Kode seperti itu jarang dapat dipertahankan apalagi efisien di tempat pertama.

Apakah dia benar

Ia benar dari perspektif efisiensi yang sangat mendasar. Tidak ada kompiler yang setahu saya dapat mengoptimalkan kode polimorfik yang melibatkan objek dan pengiriman dinamis lebih baik daripada pernyataan switch. Anda tidak akan pernah berakhir dengan LUT atau tabel lompat ke kode inline dari kode polimorfik, karena kode seperti itu cenderung berfungsi sebagai penghalang pengoptimal untuk kompiler (ia tidak akan tahu fungsi untuk memanggil hingga waktu pengiriman dinamis terjadi).

Lebih berguna untuk tidak memikirkan biaya ini dalam hal tabel lompatan tetapi lebih dalam hal penghalang optimasi. Untuk polimorfisme, pemanggilan Base.method()tidak memungkinkan kompiler mengetahui fungsi mana yang sebenarnya akan dipanggil jika methodvirtual, tidak disegel, dan dapat diganti. Karena tidak tahu fungsi mana yang sebenarnya akan dipanggil terlebih dahulu, ia tidak dapat mengoptimalkan pemanggilan fungsi dan memanfaatkan lebih banyak informasi dalam membuat keputusan optimisasi, karena ia tidak benar-benar tahu fungsi mana yang akan dipanggil di waktu kode dikompilasi.

Pengoptimal adalah yang terbaik ketika mereka dapat mengintip ke dalam panggilan fungsi dan membuat optimasi yang benar-benar meratakan penelepon dan callee, atau setidaknya mengoptimalkan penelepon untuk bekerja paling efisien dengan callee. Mereka tidak dapat melakukan itu jika mereka tidak tahu fungsi mana yang sebenarnya akan dipanggil terlebih dahulu.

Apakah dia hanya berbicara di pantatnya?

Menggunakan biaya ini, yang sering berjumlah uang, untuk membenarkan mengubah ini menjadi standar pengkodean yang diterapkan secara umum pada umumnya sangat bodoh, terutama untuk tempat-tempat yang memiliki kebutuhan ekstensibilitas. Itulah hal utama yang ingin Anda perhatikan dengan pengoptimal prematur asli: mereka ingin mengubah masalah kinerja kecil menjadi standar pengkodean yang diterapkan secara seragam di seluruh basis kode tanpa memperhatikan keberlanjutan apa pun.

Saya sedikit tersinggung dengan kutipan "hacker C lama" yang digunakan dalam jawaban yang diterima, karena saya salah satunya. Tidak semua orang yang telah melakukan koding selama beberapa dekade mulai dari perangkat keras yang sangat terbatas telah berubah menjadi pengoptimal dini. Namun saya juga pernah bertemu dan bekerja dengan mereka. Tetapi tipe-tipe itu tidak pernah mengukur hal-hal seperti misprediksi cabang atau cache misses, mereka pikir mereka tahu lebih baik, dan mendasarkan gagasan mereka tentang inefisiensi dalam basis kode produksi kompleks berdasarkan pada takhayul yang tidak berlaku hari ini dan kadang-kadang tidak pernah berlaku. Orang-orang yang benar-benar bekerja di bidang yang kritis terhadap kinerja sering memahami bahwa pengoptimalan yang efektif adalah prioritas yang efektif, dan mencoba untuk menggeneralisasi standar pengkodean yang menurunkan kemampuan pemeliharaan untuk menghemat uang adalah prioritas yang sangat tidak efektif.

Uang adalah penting ketika Anda memiliki fungsi murah yang tidak melakukan banyak pekerjaan yang disebut satu miliar kali dalam lingkaran yang sangat ketat, kinerja-kritis. Dalam hal ini, kami akhirnya menghemat 10 juta dolar. Tidak ada gunanya mencukur uang receh ketika Anda memiliki fungsi yang disebut dua kali yang biayanya saja mencapai ribuan dolar. Tidak bijaksana menghabiskan waktu Anda menawar uang selama pembelian mobil. Layak menawar lebih dari satu sen jika Anda membeli satu juta kaleng soda dari produsen. Kunci optimalisasi yang efektif adalah memahami biaya-biaya ini dalam konteksnya yang tepat. Seseorang yang mencoba menghemat uang untuk setiap pembelian dan menyarankan agar semua orang mencoba menawar lebih banyak uang, apa pun yang mereka beli bukanlah pengoptimal yang terampil.


sumber
2

Sepertinya rekan kerja Anda sangat memperhatikan kinerja. Mungkin dalam beberapa kasus struktur case / switch besar akan bekerja lebih cepat, tapi semoga kalian akan melakukan percobaan dengan melakukan tes waktu pada versi OO dan versi switch / case. Saya menduga versi OO memiliki lebih sedikit kode dan lebih mudah untuk mengikuti, memahami, dan memelihara. Saya akan memperdebatkan untuk versi OO pertama (karena pemeliharaan / keterbacaan awalnya harus lebih penting), dan hanya mempertimbangkan versi switch / case hanya jika versi OO memiliki masalah kinerja yang serius dan dapat ditunjukkan bahwa switch / case akan membuat perbaikan yang signifikan.

FrustratedWithFormsDesigner
sumber
1
Seiring dengan tes waktu, dump kode dapat membantu menunjukkan bagaimana metode pengiriman C ++ (dan C #) bekerja.
S.Lott
2

Salah satu keuntungan polymorphism yang tidak ada yang menyebutkan bahwa Anda akan dapat menyusun kode Anda lebih baik menggunakan pewarisan jika Anda selalu mengaktifkan daftar kasus yang sama, tetapi kadang-kadang beberapa kasus ditangani dengan cara yang sama dan kadang-kadang tidak

Misalnya. jika Anda beralih di antara Dog, Catdan Elephant, dan kadang-kadang Dogdan Catmemiliki kasus yang sama, Anda dapat membuat keduanya mewarisi dari kelas abstrak DomesticAnimaldan meletakkan fungsi tersebut di kelas abstrak.

Juga, saya terkejut bahwa beberapa orang menggunakan parser sebagai contoh di mana Anda tidak akan menggunakan polimorfisme. Untuk parser seperti pohon, ini jelas merupakan pendekatan yang salah, tetapi jika Anda memiliki sesuatu seperti perakitan, di mana setiap baris agak independen, dan mulai dengan opcode yang menunjukkan bagaimana sisa garis harus ditafsirkan, saya benar-benar akan menggunakan polimorfisme dan sebuah Pabrik. Setiap kelas dapat mengimplementasikan fungsi seperti ExtractConstantsatau ExtractSymbols. Saya telah menggunakan pendekatan ini untuk mainan BASIC interpreter.

jwg
sumber
Peralihan juga dapat mewarisi perilaku, melalui kasus defaultnya. "... extends BaseOperationVisitor" menjadi "default: BaseOperation (node)"
Samuel Danielson
0

"Kita harus melupakan efisiensi kecil, katakanlah sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan"

Donald Knuth

thorsten müller
sumber
0

Bahkan jika ini tidak buruk untuk pemeliharaan, saya tidak percaya itu akan lebih baik untuk kinerja. Panggilan fungsi virtual hanyalah satu tipuan ekstra (sama dengan kasus terbaik untuk pernyataan switch) sehingga bahkan di C ++ kinerjanya harus kira-kira sama. Di C #, di mana semua panggilan fungsi adalah virtual, pernyataan switch harus lebih buruk, karena Anda memiliki overhead panggilan fungsi virtual yang sama di kedua versi.

Dirk Holsopple
sumber
1
Hilang "tidak"? Dalam C #, tidak semua panggilan fungsi adalah virtual. C # bukan Java.
Ben Voigt
0

Rekan Anda tidak berbicara di belakangnya, sejauh komentar mengenai tabel lompat pergi. Namun, menggunakannya untuk membenarkan penulisan kode buruk adalah kesalahannya.

Compiler C # mengubah pernyataan switch dengan hanya beberapa case menjadi serangkaian if / else, jadi tidak lebih cepat daripada menggunakan if / else. Kompiler mengonversi pernyataan beralih yang lebih besar menjadi Kamus (tabel lompatan yang dimaksud rekan Anda). Silakan lihat jawaban ini untuk pertanyaan Stack Overflow pada topik untuk lebih jelasnya .

Pernyataan peralihan besar sulit dibaca dan dikelola. Kamus "kasing" dan fungsi jauh lebih mudah dibaca. Karena saklar itu berubah, Anda dan kolega Anda disarankan untuk menggunakan kamus secara langsung.

David Arno
sumber
0

Dia belum tentu berbicara keluar dari pantatnya. Setidaknya dalam switchpernyataan C dan C ++ dapat dioptimalkan untuk melompat tabel sementara saya belum pernah melihatnya terjadi dengan pengiriman dinamis dalam fungsi yang hanya memiliki akses ke basis pointer. Paling tidak yang terakhir membutuhkan pengoptimal yang jauh lebih cerdas melihat lebih banyak kode di sekitarnya untuk mencari tahu apa subtipe yang digunakan dari panggilan fungsi virtual melalui pointer basis / referensi.

Selain itu, pengiriman dinamis sering berfungsi sebagai "penghalang optimisasi", yang berarti kompiler sering tidak akan dapat menyejajarkan kode dan secara optimal mengalokasikan register untuk meminimalkan tumpahan tumpahan dan semua hal-hal mewah, karena tidak dapat mengetahui apa fungsi virtual akan dipanggil melalui basis pointer untuk memasukkannya dan melakukan semua optimasi optimisasinya. Saya tidak yakin Anda bahkan ingin pengoptimal menjadi sangat pintar dan mencoba untuk mengoptimalkan panggilan fungsi tidak langsung, karena itu berpotensi menyebabkan banyak cabang kode harus dihasilkan secara terpisah di tumpukan panggilan yang diberikan (fungsi yang panggilan foo->f()akan untuk menghasilkan kode mesin yang sama sekali berbeda dari kode yang memanggilbar->f() melalui basis pointer, dan fungsi yang memanggil fungsi itu kemudian harus menghasilkan dua atau lebih versi kode, dan sebagainya - jumlah kode mesin yang dihasilkan akan meledak - mungkin tidak terlalu buruk dengan jejak JIT yang menghasilkan kode dengan cepat saat menelusuri melalui jalur eksekusi panas).

Namun, karena banyak jawaban telah bergema, itu adalah alasan yang buruk untuk mendukung banyak switchpernyataan bahkan jika itu lebih cepat dijatuhkan dengan jumlah marjinal. Selain itu, ketika datang ke efisiensi mikro, hal-hal seperti percabangan dan inlining biasanya prioritas cukup rendah dibandingkan dengan hal-hal seperti pola akses memori.

Yang mengatakan, saya melompat di sini dengan jawaban yang tidak biasa. Saya ingin membuat kasus untuk pemeliharaan switchpernyataan atas solusi polimorfik ketika, dan hanya ketika, Anda tahu pasti bahwa hanya akan ada satu tempat yang perlu melakukan switch.

Contoh utama adalah pengendali acara pusat. Dalam hal ini, Anda biasanya tidak memiliki banyak tempat yang menangani acara, hanya satu (mengapa "pusat"). Untuk kasus-kasus tersebut, Anda tidak mendapatkan keuntungan dari perpanjangan yang disediakan oleh solusi polimorfik. Solusi polimorfik bermanfaat ketika ada banyak tempat yang akan melakukan switchpernyataan analogis . Jika Anda tahu pasti hanya akan ada satu, switchpernyataan dengan 15 kasus bisa jauh lebih sederhana daripada merancang kelas dasar yang diwarisi oleh 15 subtipe dengan fungsi yang ditimpa dan pabrik untuk membuat instantiate, hanya untuk kemudian digunakan dalam satu fungsi di seluruh sistem. Dalam kasus tersebut, menambahkan subtipe baru jauh lebih membosankan daripada menambahkan casepernyataan ke satu fungsi. Jika ada, saya berpendapat untuk rawatan, bukan kinerja,switch pernyataan dalam satu kasus khusus di mana Anda tidak mendapat manfaat dari ekstensibilitas apa pun.


sumber