Apakah perubahan bertahap dalam metodologi penulisan kode mempengaruhi kinerja sistem? Dan haruskah saya peduli?

35

TD; DR:

Ada beberapa kebingungan tentang apa yang saya tanyakan, jadi di sini adalah ide penggerak di balik pertanyaan:

Saya selalu ingin pertanyaannya seperti apa itu. Saya mungkin tidak mengartikulasikannya dengan baik pada awalnya. Tetapi maksud selalu " adalah kode modular, terpisah, longgar, berpasangan, dipisahkan, " lebih jelas berdasarkan sifatnya sendiri daripada " unit tunggal monolitik, melakukan segalanya di satu tempat, satu file, digabungkan dengan erat " kode. Selebihnya hanyalah perincian dan berbagai manifestasi dari ini yang saya jumpai dulu atau sekarang atau nanti. Pasti lebih lambat pada skala tertentu. Seperti disk yang tidak terdefrag, Anda harus mengambil kepingan dari mana-mana. Lebih lambat. Tentunya. Tetapi haruskah saya peduli?

Dan pertanyaannya bukan tentang ...

bukan tentang mikro-optimasi, optimasi prematur, dll. Ini bukan tentang "mengoptimalkan bagian ini atau itu sampai mati".

Lalu apa itu?

Ini adalah tentang keseluruhan metodologi dan teknik dan cara berpikir tentang penulisan kode yang muncul seiring waktu:

  • "menyuntikkan kode ini ke kelas Anda sebagai ketergantungan"
  • "tulis satu file per kelas"
  • "Pisahkan tampilan Anda dari basis data, pengontrol, domain".
  • jangan menulis kode kunci tunggal spaghetti homogen, tetapi tulis banyak komponen modular terpisah yang bekerja bersama

Ini tentang cara dan gaya kode yang saat ini - dalam dekade ini - dilihat dan didukung dalam sebagian besar kerangka kerja, yang diadvokasi di konvensi, diteruskan melalui komunitas. Ini adalah pergeseran pemikiran dari 'blok monolitik' ke 'layanan mikro'. Dan dengan itu muncul harga dalam hal kinerja dan overhead tingkat mesin, dan beberapa overhead tingkat programmer juga.

Pertanyaan Asli berikut:

Dalam bidang Ilmu Komputer, saya telah memperhatikan perubahan penting dalam pemikiran dalam hal pemrograman. Saya menemukan saran yang cukup sering seperti ini:

  • tulis kode fungsi-bijaksana yang lebih kecil (lebih dapat diuji dan dipelihara dengan cara ini)
  • refactor kode yang ada menjadi potongan kode yang lebih kecil dan lebih kecil sampai sebagian besar metode / fungsi Anda hanya beberapa baris panjang dan jelas apa tujuan mereka (yang menciptakan lebih banyak fungsi, dibandingkan dengan blok monolitik yang lebih besar)
  • tulis fungsi yang hanya melakukan satu hal - pemisahan masalah, dll (yang biasanya membuat lebih banyak fungsi dan lebih banyak bingkai pada tumpukan)
  • buat lebih banyak file (satu kelas per file, lebih banyak kelas untuk keperluan dekomposisi, untuk keperluan lapisan seperti MVC, arsitektur domain, pola desain, OO, dll, yang menciptakan lebih banyak panggilan sistem file)

Ini adalah perubahan dibandingkan dengan praktik pengkodean "lama" atau "usang" atau "spageti" di mana Anda memiliki metode yang mencakup 2500 baris, dan kelas besar dan objek dewa melakukan segalanya.

Pertanyaan saya adalah ini:

ketika panggilan turun ke kode mesin, ke 1s dan 0s, ke instruksi perakitan, ke piring-piring HDD, saya harus sama sekali prihatin bahwa kode OO saya dipisahkan dengan sempurna kelas dengan berbagai fungsi kecil dan metode refactored kecil-ke-kecil dan metode menghasilkan juga overhead ekstra banyak?

Detail

Meskipun saya tidak akrab dengan bagaimana kode OO dan panggilan metode yang ditangani di ASM pada akhirnya, dan bagaimana panggilan DB dan panggilan kompiler diterjemahkan ke lengan aktuator bergerak pada piring HDD, saya punya beberapa ide. Saya berasumsi bahwa setiap panggilan fungsi tambahan, panggilan objek, atau panggilan "#include" (dalam beberapa bahasa), menghasilkan satu set instruksi tambahan, dengan demikian meningkatkan volume kode dan menambahkan berbagai overhead "pengkabelan kode", tanpa menambahkan kode "berguna" yang sebenarnya. . Saya juga membayangkan bahwa optimasi yang baik dapat dilakukan untuk ASM sebelum benar-benar dijalankan pada perangkat keras, tetapi optimasi itu hanya dapat melakukan banyak hal juga.

Oleh karena itu, pertanyaan saya - berapa banyak overhead (dalam ruang dan kecepatan) yang memisahkan kode (kode yang dibagi menjadi ratusan file, kelas, dan pola desain, dll) benar-benar diperkenalkan dibandingkan dengan memiliki "satu metode besar yang berisi semuanya dalam satu file monolitik ", karena overhead ini?

UPDATE untuk kejelasan:

Saya mengasumsikan bahwa mengambil kode yang sama dan membaginya, refactoring keluar, decoupling menjadi lebih banyak fungsi dan objek dan metode dan kelas akan menghasilkan semakin banyak parameter melewati antara potongan kode yang lebih kecil. Karena pastinya, kode refactoring harus membuat utas terus berjalan, dan itu membutuhkan pengoperan parameter. Lebih banyak metode atau lebih banyak kelas atau lebih banyak Metode Pabrik pola desain, menghasilkan lebih banyak overhead melewatkan berbagai bit informasi lebih dari itu dalam satu kelas monolitik atau metode.

Dikatakan di suatu tempat (kutipan TBD) bahwa hingga 70% dari semua kode terdiri dari instruksi MOV ASM - memuat register CPU dengan variabel yang tepat, bukan perhitungan yang sebenarnya sedang dilakukan. Dalam kasus saya, Anda memuat waktu CPU dengan instruksi PUSH / POP untuk memberikan tautan dan parameter yang melewati berbagai bagian kode. Semakin kecil Anda membuat potongan kode, semakin banyak "tautan" overhead diperlukan. Saya prihatin bahwa keterkaitan ini menambah mengasapi dan memperlambat perangkat lunak dan saya bertanya-tanya apakah saya harus khawatir tentang ini, dan berapa banyak, jika ada sama sekali, karena generasi saat ini dan masa depan programmer yang sedang membangun perangkat lunak untuk abad berikutnya , harus hidup dengan dan menggunakan perangkat lunak yang dibangun menggunakan praktik ini.

PEMBARUAN: Banyak file

Saya menulis kode baru sekarang secara perlahan menggantikan kode lama. Secara khusus saya perhatikan bahwa salah satu kelas lama adalah ~ 3000 file baris (seperti yang disebutkan sebelumnya). Sekarang menjadi seperangkat 15-20 file yang terletak di berbagai direktori, termasuk file uji dan tidak termasuk kerangka PHP yang saya gunakan untuk mengikat beberapa hal bersama-sama. Lebih banyak file datang juga. Ketika datang ke disk I / O, memuat beberapa file lebih lambat daripada memuat satu file besar. Tentu saja tidak semua file dimuat, mereka dimuat sesuai kebutuhan, dan ada opsi cache caching dan memory caching, namun saya masih percaya bahwa loading multiple filesdibutuhkan lebih banyak pemrosesan dari loading a single filepada memori. Saya menambahkan itu untuk keprihatinan saya.

UPDATE: Ketergantungan Suntikan semuanya

Kembali ke ini setelah beberapa saat .. Saya pikir pertanyaan saya disalahpahami. Atau mungkin saya memilih untuk salah paham beberapa jawaban. Saya tidak berbicara tentang optimalisasi mikro karena beberapa jawaban telah dipilih, (setidaknya saya pikir menyebut apa yang saya bicarakan tentang optimasi mikro adalah keliru) tetapi tentang pergerakan "Kode refactor untuk melonggarkan kopling ketat", secara keseluruhan , di setiap level kode. Saya datang dari Zend Con baru-baru ini di mana gaya kode ini telah menjadi salah satu poin inti dan centerpieces dari konvensi. Decouple logic from view, view from model, model from database, dan jika Anda bisa, decouple data dari database. Ketergantungan-menyuntikkan segalanya, yang kadang-kadang berarti hanya menambahkan kode kabel (fungsi, kelas, boilerplate) yang tidak melakukan apa - apa, tetapi berfungsi sebagai titik jahitan / kait, dengan mudah menggandakan ukuran kode dalam banyak kasus.

UPDATE 2: Apakah "memisahkan kode menjadi lebih banyak file" secara signifikan mempengaruhi kinerja (di semua tingkat komputasi)

Bagaimana filosofi compartmentalize your code into multiple filesdampak komputasi saat ini (kinerja, pemanfaatan disk, manajemen memori, tugas pemrosesan CPU)?

saya berbicara tentang

Sebelum...

Di masa lalu yang hipotetis namun tidak terlalu jauh, Anda dapat dengan mudah menulis satu mono-blok dari file yang memiliki model dan tampilan dan pengontrol spaghetti atau tidak-kode-spaghetti, tetapi itu akan menjalankan semuanya begitu sudah dimuat. Melakukan beberapa tolok ukur di masa lalu dengan menggunakan kode C, saya menemukan bahwa JAUH lebih cepat untuk memuat satu file 900Mb ke dalam memori dan memprosesnya dalam potongan besar daripada memuat banyak file yang lebih kecil dan memprosesnya dalam makanan perdamaian yang lebih kecil potongan melakukan pekerjaan yang sama pada akhirnya.

.. Dan sekarang*

Hari ini saya menemukan diri saya melihat kode yang menunjukkan buku besar, yang memiliki fitur seperti .. jika item adalah "pesanan", tampilkan pesanan blok HTML. Jika item baris dapat disalin, cetak blok HTML yang menampilkan ikon dan parameter HTML di belakangnya memungkinkan Anda untuk membuat salinan. Jika item dapat dipindahkan ke atas atau ke bawah, tampilkan panah HTML yang sesuai. Dll. Saya bisa, melalui Zend Framework createpartial()panggilan, yang pada dasarnya berarti "panggil fungsi yang mengambil parameter Anda dan sisipkan ke file HTML terpisah yang juga dipanggil". Bergantung pada seberapa detail yang ingin saya dapatkan, saya dapat membuat fungsi HTML terpisah untuk bagian terkecil dari buku besar. Satu untuk panah ke atas, panah ke bawah, satu untuk "dapatkah saya menyalin item ini", dll. Mudah membuat beberapa file hanya untuk menampilkan sebagian kecil halaman web. Mengambil kode saya dan kode Zend Framework di belakang layar, sistem / tumpukan mungkin memanggil hampir 20-30 file berbeda.

Apa?

Saya tertarik pada aspek, keausan pada mesin yang dibuat dengan mengklasifikasikan kode menjadi banyak file terpisah yang lebih kecil.

Sebagai contoh, memuat lebih banyak file berarti menempatkannya di berbagai tempat sistem file, dan di berbagai tempat HDD fisik, yang berarti lebih banyak HDD mencari dan membaca waktu.

Untuk CPU itu mungkin berarti lebih banyak konteks switching dan memuat berbagai register.

Dalam sub-blok ini (pembaruan # 2) saya tertarik lebih ketat tentang bagaimana menggunakan banyak file untuk melakukan tugas yang sama yang dapat dilakukan dalam satu file, mempengaruhi kinerja sistem.

Menggunakan Zend Form API vs HTML sederhana

Saya menggunakan Zend Form API dengan praktik OO modern terbaru dan terhebat, untuk membangun formulir HTML dengan validasi, mentransformasikannya POSTmenjadi objek domain.

Butuh 35 file untuk membuatnya.

35 files = 
    = 10 fieldsets x {programmatic fieldset + fieldset manager + view template} 
    + a few supporting files

Semuanya bisa diganti dengan beberapa file HTML + PHP + JS + CSS sederhana, mungkin total 4 file ringan.

Apakah lebih baik? Apakah ini layak? ... Bayangkan memuat 35 file + banyak file perpustakaan Zend Zramework yang membuatnya berfungsi, vs 4 file sederhana.

Dennis
sumber
1
Pertanyaan yang luar biasa Saya akan melakukan benchmarking (butuh saya sehari atau lebih untuk menemukan beberapa kasus yang baik). Namun peningkatan kecepatan pada tingkat ini datang dengan biaya besar untuk keterbacaan dan biaya pengembangan. Tebakan awal saya adalah hasilnya adalah perolehan kinerja yang dapat diabaikan.
Dan Sabin
7
@Dan: Bisakah Anda memasukkannya ke dalam kalender Anda untuk membandingkan kode setelah 1, 5 dan 10 tahun pemeliharaan. Jika saya ingat saya akan memeriksa kembali hasilnya :)
mattnz
1
Ya itu kicker asli. Saya pikir kita semua sepakat untuk membuat lebih sedikit pencarian dan pemanggilan fungsi lebih cepat. Namun saya tidak dapat membayangkan kasus di mana hal itu lebih disukai daripada hal-hal seperti mempertahankan dan dengan mudah melatih anggota tim baru.
Dan Sabin
2
Untuk memperjelas apakah Anda memiliki persyaratan kecepatan khusus untuk proyek Anda. Atau apakah Anda hanya ingin kode Anda "lebih cepat!" Jika itu yang terakhir saya tidak akan khawatir tentang hal itu, kode yang cukup cepat tetapi mudah dipelihara jauh jauh lebih baik daripada kode yang lebih cepat dari cukup cepat tetapi berantakan.
Cormac Mulhall
4
Gagasan untuk menghindari panggilan fungsi karena alasan kinerja adalah jenis pemikiran gila yang benar-benar dilakukan oleh Dijkstra dalam kutipan terkenalnya tentang optimalisasi pra-dewasa. Serius, saya tidak bisa
RibaldEddie

Jawaban:

10

Pertanyaan saya adalah ini: ketika panggilan turun ke kode mesin, ke 1 dan 0, ke instruksi perakitan, haruskah saya benar-benar prihatin bahwa kode yang dipisahkan kelas dengan berbagai fungsi kecil ke kecil menghasilkan terlalu banyak overhead tambahan?

Jawaban SAYA adalah ya, Anda harus. Bukan karena Anda memiliki banyak fungsi kecil (sekali waktu overhead fungsi panggilan cukup signifikan dan Anda dapat memperlambat program Anda dengan membuat sejuta panggilan kecil dalam loop, tetapi hari ini kompiler akan menyatukannya untuk Anda dan apa yang tersisa diambil dirawat oleh algoritma prediksi mewah CPU, jadi jangan khawatir tentang itu) tetapi karena Anda akan memperkenalkan konsep layering terlalu banyak ke dalam program Anda ketika fungsionalitas terlalu kecil untuk masuk akal grok di kepala Anda. Jika Anda memiliki komponen yang lebih besar, Anda dapat yakin mereka tidak melakukan pekerjaan yang sama berulang-ulang, tetapi Anda dapat membuat program Anda sangat terperinci sehingga Anda mungkin tidak dapat benar-benar memahami jalur panggilan, dan pada akhirnya berakhir dengan sesuatu yang nyaris tidak berfungsi (dan hampir tidak bisa dipelihara).

Misalnya, saya bekerja di tempat yang menunjukkan proyek referensi untuk layanan web dengan 1 metode. Proyek ini terdiri dari 32 file .cs - untuk satu layanan web! Saya pikir ini terlalu rumit, meskipun masing-masing bagian itu kecil dan mudah dipahami dengan sendirinya, ketika sampai pada menggambarkan keseluruhan sistem, saya dengan cepat menemukan diri saya harus menelusuri panggilan hanya untuk melihat apa yang sedang dilakukan (ada terlalu banyak abstraksi yang terlibat, seperti yang Anda harapkan). Layanan web pengganti saya adalah 4 file .cs.

saya tidak mengukur kinerja seperti yang saya kira kira-kira sama saja, tetapi saya dapat menjamin tambang saya secara signifikan lebih murah untuk dipertahankan. Ketika semua orang berbicara tentang waktu programmer lebih penting daripada waktu CPU, maka buat monster kompleks yang menghabiskan banyak waktu programmer baik di dev dan pemeliharaan Anda harus bertanya-tanya bahwa mereka membuat alasan untuk perilaku buruk.

Dikatakan di suatu tempat (kutipan TBD) bahwa hingga 70% dari semua kode terdiri dari instruksi MOV ASM - memuat register CPU dengan variabel yang tepat, bukan perhitungan yang sebenarnya sedang dilakukan.

Itu adalah apa yang CPU lakukan meskipun, mereka bergerak bit dari memori ke register, menambah atau mengurangi mereka, dan kemudian menempatkan mereka kembali ke dalam memori. Semua komputasi bermuara pada hal itu. Pikiran Anda, saya pernah punya program yang sangat multi-threaded yang menghabiskan sebagian besar waktu konteksnya beralih (yaitu menyimpan dan mengembalikan register status thread) daripada bekerja pada kode thread. Kunci sederhana di tempat yang salah benar-benar mengacaukan kinerja di sana, dan itu juga kode yang tidak berbahaya.

Jadi saran saya adalah: temukan jalan tengah yang masuk akal di antara kedua ekstrem yang membuat kode Anda terlihat bagus untuk manusia lain, dan uji sistem untuk melihat apakah kinerjanya baik. Gunakan fitur OS untuk memastikan itu berjalan seperti yang Anda harapkan dengan CPU, memori, disk dan IO jaringan.

gbjbaanb
sumber
Saya pikir ini yang paling berbicara kepada saya sekarang. Ini menunjukkan kepada saya bahwa jalan tengah yang baik adalah memulai dengan konsep pemetaan pikiran ke potongan kode sambil mengikuti dan menggunakan konsep-konsep tertentu (seperti DI), daripada terikat dengan dekomposisi kode esoteris dan menjadi gila (yaitu DI semua yang Anda perlukan atau tidak).
Dennis
1
Secara pribadi, saya menemukan bahwa lebih banyak kode "" modern "" agak lebih mudah untuk diprofilkan ... Saya kira semakin banyak kode yang dapat dikelola, juga lebih mudah diprofilkan, tetapi ada batasnya, di mana memecah barang menjadi potongan-potongan kecil membuatnya kurang terpelihara ...
AK_
25

Tanpa sangat hati-hati, optimasi mikro seperti masalah ini mengarah pada kode yang tidak dapat dipertahankan.

Awalnya sepertinya ide yang bagus, profiler memberi tahu Anda kode lebih cepat dan V & V / Test / QA bahkan mengatakan bekerja. Segera bug ditemukan, perubahan persyaratan dan peningkatan yang tidak pernah dipertimbangkan diminta.

Selama umur kode proyek menurun dan menjadi kurang efisien. Kode yang dapat dipelihara akan menjadi lebih efisien daripada rekannya yang tidak dapat dipelihara karena akan menurun lebih lambat. Alasannya adalah kode membangun entropi saat diubah -

Kode yang tidak dapat dipelihara dengan cepat memiliki lebih banyak kode mati, jalur berlebihan, dan duplikasi. Ini menyebabkan lebih banyak bug, menciptakan siklus degradasi kode - termasuk kinerjanya. Tak lama, pengembang memiliki kepercayaan rendah bahwa perubahan yang mereka lakukan adalah benar. Ini memperlambat mereka, membuat orang berhati-hati dan umumnya mengarah pada entropi karena mereka hanya membahas detail yang dapat mereka lihat

Kode dapat dipertahankan, dengan modul kecil dan unit test lebih mudah untuk diubah, kode yang tidak lagi diperlukan lebih mudah untuk diidentifikasi dan dihapus. Kode yang rusak juga lebih mudah diidentifikasi, bisa diperbaiki atau diganti dengan percaya diri.

SO pada akhirnya itu turun ke manajemen siklus hidup dan tidak sesederhana "ini lebih cepat sehingga akan selalu lebih cepat."

Di atas semua itu, kode lambat yang benar jauh lebih cepat daripada kode salah cepat.

mattnz
sumber
Terima kasih. Untuk menggerakkan ini sedikit ke arah yang saya tuju, saya tidak berbicara tentang optimasi mikro, tetapi gerakan global menuju penulisan kode yang lebih kecil, memasukkan lebih banyak injeksi ketergantungan dan karenanya lebih banyak bagian fungsionalitas luar, dan lebih banyak "bagian yang bergerak" dari kode secara umum bahwa semua harus terhubung bersama agar mereka dapat bekerja. Saya cenderung berpikir bahwa ini menghasilkan lebih banyak linkage / konektor / variabel-passing / MOV / PUSH / POP / CALL / JMP bulu pada tingkat perangkat keras. Saya juga melihat nilai bergeser ke arah keterbacaan kode, meskipun dengan mengorbankan siklus komputasi tingkat perangkat keras untuk "fluff".
Dennis
10
Menghindari panggilan fungsi karena alasan kinerja benar-benar optimasi mikro! Serius. Saya tidak bisa memikirkan contoh optimasi mikro yang lebih baik. Bukti apa yang Anda miliki bahwa perbedaan kinerja sebenarnya penting untuk jenis perangkat lunak yang Anda tulis? Sepertinya Anda tidak punya.
RibaldEddie
17

Untuk pemahaman saya, seperti yang Anda tunjukkan dengan inline, pada bentuk kode yang lebih rendah seperti C ++ dapat membuat perbedaan tetapi saya katakan BISA ringan.

Situs web merangkumnya - tidak ada jawaban yang mudah . Itu tergantung pada sistem, itu tergantung pada apa aplikasi Anda lakukan, itu tergantung pada bahasa, itu tergantung pada kompiler dan optimasi.

C ++ misalnya, sebaris dapat meningkatkan kinerja. Sering kali mungkin tidak melakukan apa-apa, atau mungkin menurunkan kinerja, tetapi secara pribadi saya tidak pernah menemukan bahwa saya sendiri pernah mendengar cerita. Inline tidak lebih dari sebuah saran kepada kompiler untuk mengoptimalkan, yang dapat diabaikan.

Kemungkinannya adalah, jika Anda mengembangkan program tingkat yang lebih tinggi, biaya overhead seharusnya tidak menjadi masalah jika memang ada. Kompiler sangat pintar hari ini dan harus menangani hal ini. Banyak programmer memiliki kode-untuk-hidup oleh: jangan pernah percaya kompiler. Jika ini berlaku untuk Anda, maka sedikit saja optimasi yang Anda rasa penting bisa dilakukan. Namun, perlu diingat, setiap bahasa berbeda dalam hal ini. Java melakukan optimasi sebaris secara otomatis saat runtime. Dalam Javascript, sebaris halaman web Anda (bukan file terpisah) adalah dorongan dan setiap milidetik untuk halaman web mungkin diperhitungkan tetapi itu lebih merupakan masalah IO.

Tetapi pada program-program tingkat yang lebih rendah di mana programmer mungkin melakukan banyak pekerjaan kode mesin bersama dengan sesuatu seperti C ++, yang tidak sengaja mungkin membuat semua perbedaan. Game adalah contoh yang baik di mana perpipaan CPU sangat penting, terutama pada konsol, dan sesuatu seperti inline dapat bertambah sedikit di sana-sini.

Bacaan yang bagus tentang inline secara khusus: http://www.gotw.ca/gotw/033.htm

pengguna99999991
sumber
Berasal dari sudut pandang pertanyaan saya, saya tidak fokus pada inlining per seh, tetapi pada "pengkabelan terkodifikasi" yang menggunakan CPU, bus, dan proses waktu I / O yang menghubungkan berbagai bagian kode. Saya ingin tahu apakah ada titik di mana ada 50% atau lebih kode pengkabelan dan 50% dari kode aktual yang ingin Anda jalankan. Saya membayangkan ada banyak kesalahan bahkan dalam kode ketat yang dapat ditulis, dan tampaknya menjadi kenyataan hidup. Sebagian besar kode aktual yang berjalan pada level bit dan byte adalah logistik - memindahkan nilai dari satu tempat ke tempat lain, melompat ke satu tempat atau tempat lain, dan hanya kadang-kadang melakukan penambahan ..
Dennis
... pengurangan atau fungsi terkait bisnis lainnya. Sama seperti loop membuka gulungan dapat mempercepat beberapa loop karena lebih sedikit overhead yang dialokasikan untuk variabel tambahan, menulis fungsi yang lebih besar mungkin dapat menambah beberapa kecepatan, asalkan use case Anda diatur untuk itu. Perhatian saya di sini adalah lebih menyeluruh, melihat banyak saran untuk menulis potongan kode yang lebih kecil, meningkatkan perkabelan ini, sambil memberi manfaat dengan menambah keterbacaan (mudah-mudahan), dan dengan mengorbankan mengasapi di tingkat mikro.
Dennis
3
@ Dennis - satu hal yang perlu dipertimbangkan adalah bahwa dalam bahasa OO, mungkin ada SANGAT sedikit korelasi antara apa yang ditulis programmer (a + b) dan kode apa yang dihasilkan (penambahan sederhana dua register? Bergerak dari memori pertama? Casting, lalu fungsi memanggil ke operator objek +?). Jadi 'fungsi kecil' di tingkat programmer mungkin sama sekali tidak kecil setelah dirender menjadi kode mesin.
Michael Kohne
2
@ Dennis Saya dapat mengatakan bahwa menulis kode ASM (langsung, tidak dikompilasi) untuk Windows berjalan di sepanjang baris "mov, mov, invoke, mov, mov, invoke". Dengan memanggil menjadi makro yang melakukan panggilan yang dibungkus oleh push / pops ... Kadang-kadang Anda akan melakukan panggilan fungsi dalam kode Anda sendiri tetapi dikerdilkan oleh semua panggilan OS.
Brian Knoblauch
12

Ini adalah perubahan dibandingkan dengan praktik kode "lama" atau "buruk" di mana Anda memiliki metode yang mencakup 2500 baris, dan kelas besar melakukan semuanya.

Saya tidak berpikir ada orang yang berpikir melakukan itu adalah praktik yang baik. Dan saya ragu orang-orang yang melakukannya karena alasan kinerja.

Saya pikir kutipan terkenal dari Donald Knuth sangat relevan di sini:

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

Jadi, di 97% kode Anda, cukup gunakan praktik yang baik, tulis metode kecil (seberapa kecil masalah pendapat, saya tidak berpikir semua metode harus hanya beberapa baris), dll. Untuk 3% sisanya, di mana kinerja itu penting, mengukurnya . Dan jika pengukuran menunjukkan bahwa memiliki banyak metode kecil sebenarnya secara signifikan memperlambat kode Anda, maka Anda harus menggabungkannya menjadi metode yang lebih besar. Tetapi jangan menulis kode yang tidak dapat dipelihara hanya karena mungkin lebih cepat.

svick
sumber
11

Anda perlu berhati-hati untuk mendengarkan programmer berpengalaman serta pemikiran saat ini. Orang yang telah bertahun-tahun berurusan dengan perangkat lunak besar memiliki sesuatu untuk disumbangkan.

Dalam pengalaman saya, inilah yang menyebabkan perlambatan, dan mereka tidak kecil. Mereka adalah urutan besarnya:

  • Asumsi bahwa setiap baris kode kira-kira menghabiskan waktu sebanyak yang lainnya. Misalnya cout << endlversus a = b + c. Yang pertama membutuhkan ribuan kali lebih lama dari yang kedua. Stackexchange memiliki banyak pertanyaan dalam bentuk "Saya sudah mencoba berbagai cara untuk mengoptimalkan kode ini, tetapi sepertinya tidak ada bedanya, mengapa tidak?" ketika ada panggilan fungsi besar-tua di tengahnya.

  • Asumsi bahwa setiap fungsi atau metode panggilan, setelah ditulis, tentu saja diperlukan. Fungsi dan metode mudah dipanggil, dan panggilan biasanya cukup efisien. Masalahnya adalah mereka seperti kartu kredit. Mereka menggoda Anda untuk membelanjakan lebih dari yang sebenarnya Anda inginkan, dan mereka cenderung menyembunyikan apa yang Anda habiskan. Selain itu, perangkat lunak besar memiliki lapisan demi lapisan abstraksi, sehingga walaupun hanya ada 15% pemborosan di setiap lapisan, lebih dari 5 lapisan yang diperparah menjadi faktor perlambatan 2. Jawabannya adalah bukan untuk menghapus fungsionalitas atau menulis fungsi yang lebih besar, itu adalah untuk mendisiplinkan diri Anda untuk berjaga-jaga untuk masalah ini dan bersedia dan mampu membasmi itu .

  • Umum berderap. Nilai abstraksi adalah Anda dapat berbuat lebih banyak dengan lebih sedikit kode - setidaknya itulah harapannya. Gagasan ini bisa didorong ke ekstrem. Masalah dengan terlalu banyak generalitas adalah bahwa setiap masalah adalah spesifik, dan ketika Anda menyelesaikannya dengan abstraksi umum, abstraksi tersebut tidak selalu mampu mengeksploitasi sifat spesifik dari masalah Anda. Sebagai contoh, saya telah melihat situasi di mana kelas antrian prioritas mewah, yang bisa efisien pada ukuran besar, digunakan ketika panjangnya tidak pernah melebihi 3!

  • Struktur data berderap. OOP adalah paradigma yang sangat berguna, tetapi tidak mendorong seseorang untuk meminimalkan struktur data - melainkan mendorong seseorang untuk mencoba menyembunyikan kerumitannya. Misalnya, ada konsep "notifikasi" di mana jika datum A dimodifikasi dalam beberapa cara, A mengeluarkan acara notifikasi sehingga B dan C juga dapat memodifikasi diri mereka sendiri agar keseluruhan ensemble tetap konsisten. Ini dapat menyebar di banyak lapisan, dan memperbesar biaya modifikasi dengan sangat besar. Maka sangat mungkin bahwa perubahan ke A bisa segera dibatalkanatau diubah menjadi modifikasi lain, yang berarti bahwa upaya yang dihabiskan untuk mencoba menjaga ensemble tetap konsisten harus dilakukan lagi. Membingungkan itu adalah kemungkinan bug di semua penangan notifikasi ini, dan sirkularitas, dll. Jauh lebih baik untuk mencoba menjaga struktur data dinormalisasi, sehingga setiap perubahan perlu dilakukan di satu tempat saja. Jika data yang tidak dinormalkan tidak dapat dihindari, lebih baik memiliki kartu izin berkala untuk memperbaiki ketidakkonsistenan, daripada berpura-pura data tersebut dapat dijaga agar tetap konsisten dengan tali pendek.

... ketika saya memikirkan lebih banyak saya akan menambahkannya.

Mike Dunlavey
sumber
8

Jawaban singkatnya adalah "ya". Dan, umumnya, kode akan sedikit lebih lambat.

Tetapi kadang-kadang refactoring OO-ish yang tepat akan mengungkapkan optimasi yang membuat kode lebih cepat. Saya bekerja pada satu proyek di mana kami membuat algoritma Java yang kompleks jauh lebih OO-ish, dengan struktur data yang tepat, getter, dll. Bukannya array objek bersarang berantakan. Tetapi, dengan mengisolasi dan membatasi akses ke struktur data dengan lebih baik, kami dapat mengubah dari array raksasa Doubles (dengan nol untuk hasil kosong) menjadi array yang lebih terorganisir dari doubl, dengan NaNs untuk hasil kosong. Ini menghasilkan keuntungan 10x dalam kecepatan.

Tambahan: Secara umum, kode terstruktur yang lebih kecil dan lebih baik harus lebih menerima multi-threading, cara terbaik Anda untuk mendapatkan speedup utama.

pengguna949300
sumber
1
Saya tidak mengerti mengapa beralih dari Doubles ke doubles memerlukan kode terstruktur yang lebih baik.
svick
Wow, downvote? Kode Klien Asli tidak berurusan dengan Double.NaNs, tetapi memeriksa nol untuk mewakili nilai kosong. Setelah restrukturisasi, kita dapat menangani ini (melalui enkapsulasi) dengan getter dari berbagai hasil algoritma. Tentu, kami bisa menulis ulang kode klien, tetapi ini lebih mudah.
user949300
Sebagai catatan, saya bukan orang yang menurunkan jawaban ini.
svick
7

Antara lain, pemrograman adalah tentang pertukaran . Berdasarkan fakta ini, saya cenderung menjawab ya, itu bisa lebih lambat. Tetapi pikirkan kembali apa yang Anda dapatkan. Mendapatkan kode yang mudah dibaca, dapat digunakan kembali, dan mudah dimodifikasi dengan mudah mengalahkan segala kemungkinan kelemahan.

Seperti yang disebutkan @ user949300 , lebih mudah untuk menemukan area yang dapat ditingkatkan secara algoritmik atau arsitektur dengan pendekatan tersebut; meningkatkan mereka biasanya jauh lebih bermanfaat dan efektif daripada tidak memiliki mungkin OO atau fungsi-memanggil overhead (yang sudah hanya kebisingan, saya bertaruh).


Saya juga membayangkan bahwa optimasi yang baik dapat dilakukan untuk ASM sebelum benar-benar dijalankan pada perangkat keras.

Setiap kali sesuatu seperti ini terlintas dalam pikiran saya, saya ingat bahwa dekade yang dihabiskan oleh orang-orang paling pintar yang bekerja pada kompiler mungkin membuat alat seperti GCC jauh lebih baik daripada saya dalam menghasilkan kode mesin. Kecuali Anda mengerjakan beberapa hal terkait mikrokontroler, saya sarankan Anda tidak khawatir tentang itu.

Saya berasumsi bahwa menambahkan lebih banyak fungsi dan lebih banyak objek dan kelas dalam suatu kode akan menghasilkan lebih banyak dan lebih banyak parameter yang lewat di antara potongan kode yang lebih kecil.

Dengan asumsi apa pun ketika mengoptimalkan adalah buang-buang waktu, Anda perlu fakta tentang kinerja kode. Temukan di mana program Anda menghabiskan sebagian besar waktu dengan alat khusus, optimalkan, iterate.


Untuk meringkas semuanya; biarkan kompiler melakukan tugasnya, fokus pada hal-hal penting seperti meningkatkan algoritme dan struktur data Anda. Semua pola yang Anda sebutkan dalam pertanyaan Anda ada untuk membantu Anda, gunakan itu.

PS: Pembicaraan 2 Crockford ini muncul di kepala saya dan saya pikir mereka agak terkait. Yang pertama adalah sejarah CS super singkat (yang selalu baik untuk diketahui dengan ilmu pasti); dan yang kedua adalah tentang mengapa kita menolak hal-hal baik.

elmigranto
sumber
Saya sangat suka jawaban ini. Manusia mengerikan dalam menebak kompiler dan menembak di mana kemacetan berada. tentu saja kompleksitas waktu-O besar adalah sesuatu yang harus Anda waspadai, tetapi metode spaghetti 100-line besar versus pemanggilan metode pabrik + beberapa metode pengiriman virtual seharusnya tidak pernah menjadi diskusi. kinerja sama sekali tidak menarik dalam hal itu. juga, plus besar untuk mencatat bahwa "mengoptimalkan" tanpa fakta dan pengukuran sulit adalah buang-buang waktu.
sara
4

Saya percaya tren yang Anda identifikasi benar pada pengembangan perangkat lunak - waktu programmer lebih mahal daripada waktu CPU. Sejauh ini, komputer hanya menjadi lebih cepat dan lebih murah, tetapi kekacauan aplikasi yang berantakan dapat membutuhkan ratusan bahkan ribuan jam kerja untuk berubah. Mengingat biaya gaji, tunjangan, ruang kantor, dll, dll, itu lebih hemat biaya untuk memiliki kode yang mungkin mengeksekusi sedikit lebih lambat tetapi lebih cepat dan lebih aman untuk berubah.

Rory Hunter
sumber
1
Saya setuju, tetapi perangkat seluler menjadi sangat populer sehingga saya pikir itu pengecualian besar. Meskipun kekuatan pemrosesan meningkat, Anda tidak dapat membangun aplikasi iPhone dan berharap dapat menambahkan memori seperti yang Anda bisa di server web.
JeffO
3
@ Jeffoff: Saya tidak setuju - Perangkat seluler dengan prosesor quad core sekarang normal, kinerjanya (terutama karena berdampak pada usia baterai), sementara masalah, kurang penting daripada stabilitas. Ponsel atau tablet yang lambat mendapat ulasan buruk, yang tidak stabil akan dibantai. Aplikasi seluler sangat dinamis, berubah hampir setiap hari - sumbernya harus mengikuti.
mattnz
1
@ Jeffoff: Untuk apa nilainya, mesin virtual yang digunakan pada Android sangat buruk. Menurut tolok ukur itu mungkin urutan besarnya lebih lambat dari kode asli (sedangkan yang terbaik dari jenis biasanya hanya sedikit lebih lambat). Coba tebak, tidak ada yang peduli. Menulis aplikasi cepat dan CPU duduk di sana memutar-mutar ibu jari dan menunggu input pengguna 90% dari waktu.
Jan Hudec
Ada lebih banyak kinerja daripada tolok ukur CPU mentah. Ponsel Android saya berfungsi dengan baik, kecuali ketika AV memindai pembaruan dan kemudian tampaknya hanya bertahan lebih lama dari yang saya suka, dan itu adalah model RAM 2Gb quad-core! Bandwidth saat ini (apakah jaringan atau memori) mungkin merupakan hambatan utama. CPU supercepat Anda mungkin memutar-mutar ibu jari 99% dari waktu dan pengalaman keseluruhan masih buruk.
gbjbaanb
4

Nah 20+ tahun yang lalu, yang saya kira Anda tidak menelepon baru dan tidak "tua atau buruk", aturannya adalah untuk menjaga fungsi cukup kecil agar muat di halaman yang dicetak. Kami memiliki printer dot matrix sehingga jumlah garis agak tetap umumnya hanya satu atau dua pilihan untuk jumlah baris per halaman ... pasti kurang dari 2500 baris.

Anda bertanya banyak sisi masalah, rawatan, kinerja, uji coba, keterbacaan. Semakin Anda condong ke arah kinerja, kode tersebut semakin tidak dapat dipelihara dan dapat dibaca, sehingga Anda harus menemukan tingkat kenyamanan Anda yang dapat dan akan bervariasi untuk masing-masing programmer.

Sejauh kode yang dihasilkan oleh kompiler (kode mesin jika Anda mau), semakin besar fungsinya semakin banyak kesempatan untuk perlu menumpahkan nilai menengah dalam register ke stack. Ketika frame stack digunakan, konsumsi stack dalam potongan yang lebih besar. Semakin kecil fungsinya, semakin banyak peluang untuk data tetap lebih banyak di register dan semakin sedikit ketergantungan pada stack. Potongan stack yang lebih kecil diperlukan per fungsi secara alami. Stack frames memiliki pro dan kontra untuk kinerja. Fungsi yang lebih kecil berarti lebih banyak pengaturan dan pembersihan fungsi. Tentu saja itu juga tergantung pada bagaimana Anda mengkompilasi, peluang apa yang Anda berikan kepada kompiler. Anda mungkin memiliki fungsi 250 10 baris alih-alih satu fungsi 2500 baris, satu fungsi 2500 baris yang akan didapat oleh kompiler jika dapat / memilih untuk mengoptimalkan seluruh hal. Tetapi jika Anda mengambil fungsi-fungsi 250 10 baris dan menyebarkannya di 2, 3, 4, 250 file terpisah, kompilasi setiap file secara terpisah maka kompiler tidak akan dapat mengoptimalkan kode mati hampir sebanyak yang bisa dimiliki. Intinya di sini adalah ada pro dan kontra untuk keduanya dan tidak mungkin untuk menempatkan aturan umum tentang ini atau itu adalah cara terbaik.

Fungsi berukuran wajar, sesuatu yang dapat dilihat seseorang di layar atau halaman (dalam font yang masuk akal), adalah sesuatu yang dapat mereka konsumsi dengan lebih baik dan memahami daripada kode yang cukup besar. Tetapi jika itu hanya fungsi kecil dengan panggilan ke banyak fungsi kecil lainnya yang memanggil banyak fungsi kecil lainnya, Anda memerlukan beberapa jendela atau browser untuk memahami kode itu sehingga Anda tidak membeli apa pun di sisi keterbacaan.

cara unix adalah menggunakan istilah saya, blok lego yang dipoles dengan baik. Mengapa Anda menggunakan fungsi kaset ini bertahun-tahun setelah kami berhenti menggunakan kaset? Karena gumpalan melakukan tugasnya dengan sangat baik dan di sisi belakang kita dapat mengganti antarmuka pita dengan antarmuka file dan mengambil keuntungan dari daging program. Mengapa menulis ulang perangkat lunak pembakar cdrom hanya karena scsi hilang sebagai antarmuka dominan digantikan oleh ide (kemudian kembali lagi nanti). Sekali lagi gunakan dukungan dari sub blok yang dipoles dan ganti salah satu ujungnya dengan blok antarmuka baru (juga pahami para perancang perangkat keras cukup memasang blok antarmuka pada desain perangkat keras dalam beberapa kasus membuat scsi drive memiliki ide untuk antarmuka scsi. Untuk mempersingkat ini , bangun blok lego yang dipoles berukuran wajar, masing-masing dengan tujuan dan input dan output yang didefinisikan dengan baik. Anda dapat membungkus tes di sekitar blok-blok lego dan kemudian mengambil blok yang sama dan membungkus antarmuka pengguna dan antarmuka sistem operasi di sekitar blok yang sama dan blok, secara teori sedang diuji dengan baik dan dipahami dengan baik tidak perlu di-debug, hanya blok-blok baru tambahan yang ditambahkan di setiap ujung. selama semua antarmuka blok Anda dirancang dengan baik dan fungsinya dipahami dengan baik, Anda dapat membangun banyak hal dengan lem minimal jika ada. seperti halnya dengan balok lego biru dan merah dan hitam dan kuning dengan ukuran dan bentuk yang diketahui, Anda dapat membuat banyak hal. selama semua antarmuka blok Anda dirancang dengan baik dan fungsinya dipahami dengan baik, Anda dapat membangun banyak hal dengan lem minimal jika ada. seperti halnya dengan balok lego biru dan merah dan hitam dan kuning dengan ukuran dan bentuk yang diketahui, Anda dapat membuat banyak hal. selama semua antarmuka blok Anda dirancang dengan baik dan fungsinya dipahami dengan baik, Anda dapat membangun banyak hal dengan lem minimal jika ada. seperti halnya dengan balok lego biru dan merah dan hitam dan kuning dengan ukuran dan bentuk yang diketahui, Anda dapat membuat banyak hal.

Setiap individu berbeda, definisi mereka dipoles dan didefinisikan dengan baik dan diuji dan dibaca bervariasi. Bukan tidak masuk akal misalnya bagi seorang profesor untuk mendikte aturan pemrograman bukan karena mereka mungkin atau mungkin tidak buruk bagi Anda sebagai seorang profesional, tetapi dalam beberapa kasus membuat pekerjaan membaca dan menilai kode Anda lebih mudah pada profesor atau asisten mahasiswa pascasarjana ... Anda sama-sama cenderung, secara profesional, untuk menemukan bahwa setiap pekerjaan mungkin memiliki aturan yang berbeda karena berbagai alasan, biasanya satu atau beberapa orang yang berkuasa memiliki pendapat mereka tentang sesuatu, benar atau salah dan dengan kekuatan itu mereka dapat menentukan bahwa Anda melakukan ada cara (atau berhenti, dipecat, atau entah bagaimana berkuasa). Aturan-aturan ini sering didasarkan pada opini karena didasarkan pada beberapa jenis fakta tentang keterbacaan, kinerja, testabilitas, portabilitas.

old_timer
sumber
"Jadi Anda harus menemukan tingkat kenyamanan Anda yang dapat dan akan bervariasi untuk setiap programmer individu" Saya pikir tingkat optimasi harus ditentukan oleh persyaratan kinerja masing-masing bagian kode, bukan oleh apa yang disukai setiap programmer.
svick
Compiler yakin, dengan asumsi programmer telah memberitahu compiler untuk mengoptimalkan compiler secara umum default untuk tidak mengoptimalkan (pada baris perintah IDE mungkin memiliki default yang berbeda jika digunakan). Tetapi programmer mungkin tidak cukup tahu untuk mengoptimalkan ukuran fungsi dan dengan sebanyak mungkin target di mana ia sia-sia? Kinerja tuning tangan memiliki kecenderungan untuk memiliki efek negatif pada keterbacaan, dan rawatan khususnya, dapat diuji dengan cara baik.
old_timer
2

Tergantung seberapa pintar kompiler Anda. Secara umum, mencoba mengakali pengoptimal adalah ide yang buruk dan sebenarnya dapat merampas kompiler peluang untuk mengoptimalkan. Sebagai permulaan, Anda mungkin tidak memiliki petunjuk apa pun yang dapat dilakukan dan sebagian besar dari apa yang Anda lakukan benar-benar memengaruhi seberapa baik ia melakukannya juga.

Optimalisasi prematur adalah gagasan programmer mencoba melakukan itu dan berakhir dengan sulit untuk mempertahankan kode yang tidak benar-benar di jalur kritis dari apa yang mereka coba lakukan. Mencoba untuk memeras CPU sebanyak mungkin ketika sebagian besar waktu aplikasi Anda benar-benar diblokir menunggu peristiwa IO, adalah sesuatu yang saya lihat banyak contohnya.

Yang terbaik adalah kode untuk kebenaran dan menggunakan profiler untuk menemukan hambatan kinerja yang sebenarnya, dan memperbaikinya dengan menganalisis apa yang ada di jalur kritis dan apakah itu dapat diperbaiki. Seringkali beberapa perbaikan sederhana berjalan jauh.

Jilles van Gurp
sumber
0

[elemen waktu-komputer]

Apakah Refactoring ke arah Looser Coupling dan Fungsi yang Lebih Kecil mempengaruhi Kecepatan Kode?

Ya, benar. Tetapi terserah kepada penerjemah, kompiler, dan kompiler JIT untuk menghapus kode "jahitan / kabel" ini, dan beberapa melakukannya lebih baik daripada yang lain, tetapi beberapa tidak.

Perhatian multi-file menambah overhead I / O, sehingga mempengaruhi kecepatan juga (dalam waktu komputer).

[elemen kecepatan manusia]

(Dan haruskah saya peduli?)

tidak, kamu tidak seharusnya peduli. Komputer dan sirkuit sangat cepat akhir-akhir ini, dan faktor-faktor lain mengambil alih, seperti latensi jaringan, basis data I / O, dan caching.

Jadi 2x - 4x memperlambat dalam eksekusi kode asli itu sendiri sering akan tenggelam oleh faktor-faktor lain.

Sejauh memuat banyak file, itu sering diurus oleh berbagai solusi caching. Mungkin perlu waktu lebih lama untuk memuat berbagai hal dan menggabungkannya untuk pertama kali, tetapi untuk setiap waktu berikutnya, untuk file statis, caching berfungsi seolah-olah satu file sedang dimuat. Caching datang sebagai solusi untuk memuat banyak file.

Dennis
sumber
0

JAWABAN (jika Anda melewatkannya)

Ya, Anda harus peduli, tetapi peduli tentang bagaimana Anda menulis kode, dan bukan tentang kinerja.

Pendeknya

Jangan pedulikan kinerja

Dalam konteks pertanyaan, kompiler dan penerjemah yang lebih pintar sudah mengatasinya

Jangan pedulikan menulis kode yang bisa dipelihara

Kode di mana biaya pemeliharaan berada pada tingkat pemahaman manusia yang masuk akal. yaitu tidak menulis 1000 fungsi yang lebih kecil membuat kode tidak dapat dipahami bahkan jika Anda memahami masing-masing, dan jangan menulis 1 fungsi objek dewa yang terlalu besar untuk dipahami, tetapi menulis 10 fungsi rekayasa yang masuk akal bagi manusia, dan mudah dirawat.

Dennis
sumber