OK, Anda mendefinisikan masalah di mana tampaknya tidak ada banyak ruang untuk perbaikan. Itu cukup langka, menurut pengalaman saya. Saya mencoba menjelaskan ini dalam artikel Dr. Dobbs pada bulan November 1993, dengan memulai dari program non-sepele yang dirancang secara konvensional tanpa limbah yang jelas dan membawanya melalui serangkaian optimisasi hingga waktu jam dinding berkurang dari 48 detik. menjadi 1,1 detik, dan ukuran kode sumber dikurangi dengan faktor 4. Alat diagnostik saya adalah ini . Urutan perubahan adalah ini:
Masalah pertama yang ditemukan adalah penggunaan daftar cluster (sekarang disebut "iterators" dan "kelas kontainer") terhitung lebih dari separuh waktu. Itu diganti dengan kode yang cukup sederhana, membawa waktu ke 20 detik.
Sekarang pengambil waktu terbesar lebih banyak membangun daftar. Sebagai persentase, sebelumnya tidak terlalu besar, tetapi sekarang karena masalah yang lebih besar telah dihapus. Saya menemukan cara untuk mempercepatnya, dan waktu turun menjadi 17 detik.
Sekarang lebih sulit untuk menemukan penyebab yang jelas, tetapi ada beberapa yang lebih kecil yang dapat saya lakukan sesuatu, dan waktu turun menjadi 13 detik.
Sekarang saya sepertinya telah menabrak tembok. Sampel memberi tahu saya persis apa yang dilakukannya, tetapi saya tidak dapat menemukan apa pun yang dapat saya perbaiki. Kemudian saya merenungkan desain dasar program, pada struktur yang didorong transaksi, dan bertanya apakah semua pencarian daftar yang dilakukannya benar-benar diamanatkan oleh persyaratan masalah.
Kemudian saya menemukan desain ulang, di mana kode program sebenarnya dihasilkan (melalui macro preprocessor) dari satu set sumber yang lebih kecil, dan di mana program tidak terus-menerus mencari tahu hal-hal yang diketahui oleh pemrogram cukup dapat diprediksi. Dengan kata lain, jangan "menafsirkan" urutan hal yang harus dilakukan, "kompilasi".
- Perancangan ulang itu dilakukan, mengecilkan kode sumber dengan faktor 4, dan waktu dikurangi menjadi 10 detik.
Sekarang, karena semakin cepat, sulit untuk dicoba, jadi saya berikan 10 kali lebih banyak pekerjaan yang harus dilakukan, tetapi waktu berikut ini didasarkan pada beban kerja asli.
Lebih banyak diagnosis mengungkapkan bahwa ia menghabiskan waktu dalam manajemen antrian. Sejalan ini mengurangi waktu hingga 7 detik.
Sekarang pengambil waktu yang besar adalah pencetakan diagnostik yang telah saya lakukan. Siram itu - 4 detik.
Sekarang pencatat waktu terbesar adalah panggilan ke malloc dan gratis . Mendaur ulang objek - 2,6 detik.
Melanjutkan ke sampel, saya masih menemukan operasi yang tidak sepenuhnya diperlukan - 1,1 detik.
Total faktor percepatan: 43.6
Sekarang tidak ada dua program yang sama, tetapi dalam perangkat lunak non-mainan saya selalu melihat perkembangan seperti ini. Pertama Anda mendapatkan hal-hal yang mudah, dan kemudian yang lebih sulit, sampai Anda mencapai titik pengembalian yang semakin berkurang. Kemudian wawasan yang Anda peroleh mungkin mengarah pada desain ulang, memulai putaran baru percepatan, hingga Anda kembali mendapatkan hasil yang semakin berkurang. Sekarang ini adalah titik di mana ia mungkin masuk akal untuk bertanya-tanya apakah ++i
atau i++
atau for(;;)
atau while(1)
lebih cepat: jenis pertanyaan saya melihat begitu sering di Stack Overflow.
PS Mungkin bertanya-tanya mengapa saya tidak menggunakan profiler. Jawabannya adalah bahwa hampir setiap "masalah" ini adalah situs panggilan fungsi, yang menumpuk sampel dengan tepat. Profiler, bahkan hari ini, hampir tidak menyangkal bahwa pernyataan dan instruksi panggilan lebih penting untuk ditemukan, dan lebih mudah diperbaiki, daripada seluruh fungsi.
Saya benar-benar membangun profiler untuk melakukan ini, tetapi untuk keintiman nyata turun-dan-kotor dengan apa yang dilakukan kode, tidak ada pengganti untuk mendapatkan jari Anda tepat di dalamnya. Bukan masalah bahwa jumlah sampel kecil, karena tidak ada masalah yang ditemukan sangat kecil sehingga mereka mudah terjawab.
TAMBAH: jerryjvl meminta beberapa contoh. Inilah masalah pertama. Ini terdiri dari sejumlah kecil baris kode terpisah, bersama-sama mengambil alih separuh waktu:
/* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)
Ini menggunakan daftar cluster ILST (mirip dengan kelas daftar). Mereka diimplementasikan dengan cara biasa, dengan "menyembunyikan informasi" yang berarti bahwa pengguna kelas tidak harus peduli bagaimana mereka diimplementasikan. Ketika baris-baris ini ditulis (dari sekitar 800 baris kode) pemikiran tidak diberikan pada gagasan bahwa ini bisa menjadi "bottleneck" (Saya benci kata itu). Mereka hanyalah cara yang disarankan untuk melakukan sesuatu. Mudah untuk mengatakan di belakang bahwa ini harus dihindari, tetapi dalam pengalaman saya semua masalah kinerja seperti itu. Secara umum, ada baiknya mencoba menghindari menciptakan masalah kinerja. Bahkan lebih baik untuk menemukan dan memperbaiki yang dibuat, meskipun mereka "seharusnya dihindari" (di belakang).
Inilah masalah kedua, dalam dua baris terpisah:
/* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)
Ini adalah daftar bangunan dengan menambahkan item ke ujungnya. (Cara mengatasinya adalah mengumpulkan item dalam array, dan membuat daftar sekaligus.) Yang menarik adalah bahwa pernyataan ini hanya berharga (mis. Ada di tumpukan panggilan) 3/48 dari waktu asli, jadi mereka tidak ada dalam Bahkan masalah besar di awal . Namun, setelah menghilangkan masalah pertama, harganya 3/20 dari waktu dan sekarang menjadi "ikan yang lebih besar". Secara umum, begitulah caranya.
Saya dapat menambahkan bahwa proyek ini disuling dari proyek nyata yang saya bantu. Dalam proyek itu, masalah kinerja jauh lebih dramatis (seperti halnya speedup), seperti memanggil rutin akses database dalam loop batin untuk melihat apakah tugas selesai.
REFERENSI DITAMBAH: Kode sumber, baik yang asli maupun yang dirancang ulang, dapat ditemukan di www.ddj.com , untuk tahun 1993, dalam file 9311.zip, file slug.asc dan slug.zip.
EDIT 2011/11/26: Sekarang ada proyek SourceForge yang berisi kode sumber dalam Visual C ++ dan deskripsi blow-by-blow tentang bagaimana itu disetel. Itu hanya melewati paruh pertama skenario yang dijelaskan di atas, dan tidak mengikuti urutan yang persis sama, tetapi masih mendapat urutan 2-3 percepatan magnitudo.