Pendekatan terhadap basis kode menjadi lambat secara seragam


11

Kami sedang mengerjakan basis kode C ++ berukuran sedang (10Mloc) yang melalui upaya optimalisasi kami menjadi sangat lambat .

Basis kode ini adalah satu set perpustakaan yang kami gabungkan untuk membuatnya berfungsi. Ketika kerangka umum bagaimana perpustakaan ini berkomunikasi dikembangkan ada beberapa penekanan pada kinerja dan kemudian, ketika lebih banyak bagian di mana ditambahkan, kerangka umum tidak banyak berubah. Optimalisasi dilakukan ketika dibutuhkan dan seiring dengan perkembangan perangkat keras kami. Ini membuat keputusan awal yang mahal terlihat hanya beberapa saat kemudian. Kami sekarang pada titik di mana optimasi lebih lanjut jauh lebih mahal karena mereka akan membutuhkan penulisan ulang sebagian besar basis kode. Kami menemukan diri kami mendekati minimum lokal yang tidak diinginkan karena kami tahu bahwa pada prinsipnya kode harus dapat berjalan lebih cepat.

Apakah ada metodologi yang berhasil yang membantu memutuskan apa yang akan mengambil alih evolusi basis kode menuju solusi yang bekerja secara global yang optimal yang tidak mudah dikacaukan oleh peluang optimisasi yang mudah?

EDIT

Untuk menjawab pertanyaan bagaimana kami saat ini profil:

Kami benar-benar hanya memiliki 2 skenario berbeda bagaimana kode ini dapat digunakan, keduanya paralel paralel. Pembuatan profil dilakukan baik dengan waktu jam dinding yang dirata-ratakan atas sampel besar input dan proses yang lebih rinci (biaya instruksi, misprediksi cabang, dan masalah caching). Ini bekerja dengan baik karena kami berjalan secara eksklusif pada mesin kami yang sangat homogen (sekelompok beberapa ribu mesin yang identik). Karena kami biasanya membuat semua mesin kami sibuk sebagian besar waktu berjalan lebih cepat berarti kami dapat melihat hal-hal baru tambahan. Masalahnya tentu saja bahwa ketika variasi input baru muncul, mereka mungkin mendapatkan hukuman terlambat karena kami menghapus inefisiensi mikro yang paling jelas untuk kasus penggunaan lainnya, sehingga mungkin mempersempit jumlah skenario "berjalan secara optimal".


10
10Mloc sebenarnya adalah proyek besar
BЈовић

1
Ini 10 juta loc (SI awalan) yang dihitung oleh sloc. Saya menyebutnya "ukuran sedang" karena saya tidak tahu apa yang dianggap "besar" di sini.
Benjamin Bannier

5
cukup yakin 10 juta setidaknya besar di mana-mana dan mungkin sebagian besar tempat besar.
Ryathal

1
Luar biasa, terima kasih @honk Untuk 10M LOC sepertinya Anda mengoptimalkan pada tingkat yang sangat rendah, hampir di tingkat perangkat keras? OOP (array "struktur") tradisional sangat tidak efisien pada cache, sudahkah Anda mencoba mengatur ulang kelas Anda menjadi SOA (struktur susunan) sehingga titik data yang dikerjakan oleh kode Anda koheren dalam memori? Dengan begitu banyak mesin yang Anda jalankan ke penyumbatan komunikasi atau sinkronisasi memakan waktu? Pertanyaan terakhir, apakah Anda berurusan dengan data streaming volume tinggi atau apakah ini sebagian besar merupakan masalah operasi kompleks pada set data Anda?
Patrick Hughes

1
Ketika Anda memiliki kode sebanyak itu, peluang mulai dari yang sangat baik hingga yang mempertaruhkan hidup Anda bahwa ada percepatan potensial besar dari jenis non-lokal yang saya sebutkan. Tidak ada bedanya jika ada ribuan utas / proses. Beberapa jeda acak akan memberi mereka jari untuk Anda, atau membuktikan saya salah.
Mike Dunlavey

Jawaban:


9

Saya tidak tahu tentang pendekatan tujuan umum untuk masalah ini, tetapi dua pendekatan yang agak terkait bekerja dengan baik bagi saya di masa lalu: karena kurangnya istilah yang lebih baik, saya menyebut mereka pengelompokan agregasi dan optimasi horizontal .

Pendekatan Bunching adalah upaya untuk mengganti sejumlah besar operasi pendek, cepat dengan operasi tunggal, berjalan lebih lambat, yang sangat terspesialisasi yang pada akhirnya menghasilkan hasil yang sama.

Contoh: Setelah membuat profil satu operasi yang sangat lambat dari editor aturan visual kami, kami tidak menemukan "buah yang tergantung rendah": tidak ada satu operasi pun yang mengambil lebih dari 2% dari waktu eksekusi, namun operasi secara keseluruhan terasa lamban. Namun, kami menemukan bahwa editor mengirim sejumlah besar permintaan kecil ke server. Meskipun editor dengan cepat memproses balasan individu, jumlah interaksi permintaan / tanggapan memiliki efek multiplikatif, sehingga keseluruhan waktu operasi berlangsung beberapa detik. Setelah dengan hati-hati membuat katalog interaksi editor selama operasi yang berjalan lama itu, kami menambahkan perintah baru ke antarmuka server. Perintah tambahan lebih khusus, karena menerima data yang diperlukan untuk melakukan subset operasi singkat, mengeksplorasi dependensi data untuk mengetahui set data terakhir yang akan dikembalikan, dan memberikan respons yang berisi informasi yang diperlukan untuk menyelesaikan semua operasi kecil individu dalam satu perjalanan ke server. Ini tidak mengurangi waktu pemrosesan dalam kode kami, tetapi mengurangi jumlah latensi yang sangat signifikan karena menghapus beberapa perjalanan pulang-pergi klien-server yang mahal.

Optimalisasi horizontal adalah teknik terkait ketika Anda menghilangkan "kelambatan" yang didistribusikan secara tipis di antara beberapa komponen sistem Anda menggunakan fitur tertentu dari lingkungan eksekusi Anda.

Contoh: Setelah membuat profil operasi yang berjalan lama kami menemukan bahwa kami membuat banyak panggilan melintasi batas domain aplikasi (ini sangat spesifik untuk .NET). Kami tidak dapat menghilangkan salah satu panggilan, dan kami tidak dapat mengelompokkannya: mereka datang pada waktu yang berbeda dari bagian sistem kami yang sangat berbeda, dan hal-hal yang mereka minta bergantung pada hasil yang dikembalikan dari permintaan sebelumnya. Setiap panggilan diperlukan serialisasi dan deserialisasi dari sejumlah kecil data. Sekali lagi, masing-masing panggilan berdurasi pendek, tetapi jumlahnya sangat besar. Kami akhirnya merancang skema yang menghindari serialisasi hampir seluruhnya, menggantinya dengan melewatkan pointer melintasi batas domain aplikasi. Ini adalah kemenangan besar, karena banyak permintaan dari kelas yang sepenuhnya tidak berhubungan langsung menjadi lebih cepat sebagai hasil dari penerapan satusolusi horisontal .


Terima kasih telah berbagi pengalaman Anda, ini adalah optimasi berguna yang perlu diingat. Juga, karena mereka mengangkat bagian yang bermasalah ke tempat yang berbeda, mereka akan jauh lebih baik untuk dikontrol di masa depan. Dalam arti mereka menempatkan apa yang seharusnya terjadi di tempat pertama, sekarang hanya kembali dengan data keras.
Benjamin Bannier

3

Ini membuat keputusan awal yang mahal terlihat hanya beberapa saat kemudian. Kami sekarang pada titik di mana optimasi lebih lanjut jauh lebih mahal karena mereka akan membutuhkan penulisan ulang sebagian besar basis kode.

Ketika Anda memulai penulisan ulang ini, Anda harus melakukan beberapa hal secara berbeda.

Pertama. Dan yang paling penting. Berhenti "mengoptimalkan". "Optimasi" sama sekali tidak masalah. Seperti yang Anda lihat, hanya menulis ulang grosir yang penting.

Karena itu.

Kedua. Memahami implikasi dari setiap struktur data dan pilihan algoritma.

Ketiga. Buat pilihan aktual dari struktur data dan algoritme soal "keterlambatan pengikatan". Desain antarmuka yang dapat memiliki salah satu dari beberapa implementasi yang digunakan di belakang antarmuka.

Apa yang Anda lakukan sekarang (menulis ulang) harus jauh, jauh lebih tidak menyakitkan jika Anda memiliki seperangkat antarmuka yang memungkinkan Anda untuk membuat perubahan besar pada struktur data atau algoritma.


1
Terima kasih atas jawaban anda. Sementara kita masih perlu mengoptimalkan (yang berada di bawah 1. dan 2. untuk saya) saya sangat suka pemikiran yang terorganisir di belakang 3. Dengan membuat struktur data, algoritma & akses yang didefinisikan terlambat dan eksplisit, kita harus bisa mendapatkan pegangan pada banyak masalah yang kita hadapi. Terima kasih telah memasukkannya ke bahasa yang koheren.
Benjamin Bannier

Anda sebenarnya tidak perlu mengoptimalkan. Setelah Anda memiliki struktur data yang benar, pengoptimalan akan ditampilkan sebagai usaha yang sia-sia. Profiling akan menunjukkan di mana Anda memiliki struktur data yang salah dan algoritma yang salah. Bermain-main dengan perbedaan kinerja antara ++dan +=1akan menjadi tidak relevan dan hampir tidak terukur. Ini hal yang Anda bertahan lama .
S.Lott

1
Tidak semua algoritma buruk dapat ditemukan dengan alasan murni. Sesekali seseorang perlu duduk dan profil. Ini adalah satu-satunya cara untuk mengetahui apakah tebakan awal itu benar. Itulah satu-satunya cara untuk memperkirakan biaya riil (BigO + const).
Benjamin Bannier

Profiling akan mengungkapkan algoritma yang buruk. Benar sekali. Itu masih bukan "optimasi". Itu masih koreksi cacat desain mendasar saya membuat perubahan desain. Optimalisasi (tweaking, fine-tuning, dll.) Jarang terlihat oleh profil.
S.Lott

3

Trik praktis yang bagus adalah dengan menggunakan unit test unit Anda sebagai suite test kinerja .

Pendekatan berikut bekerja dengan baik di basis kode saya:

  1. Pastikan cakupan tes unit Anda baik (Anda sudah melakukannya, bukan?)
  2. Pastikan kerangka kerja pengujian Anda melaporkan runtime pada setiap pengujian individual . Ini penting karena Anda ingin menemukan di mana kinerja lambat terjadi.
  3. Jika tes berjalan lambat, gunakan ini sebagai cara untuk menyelam dan menargetkan optimasi di area ini . Mengoptimalkan sebelum mengidentifikasi masalah kinerja mungkin dianggap prematur, sehingga hal terbaik tentang pendekatan ini adalah Anda mendapatkan bukti nyata dari kinerja yang buruk terlebih dahulu. Jika perlu, bagi tes menjadi tes yang lebih kecil yang membandingkan aspek yang berbeda sehingga Anda dapat mengidentifikasi di mana akar masalahnya.
  4. Jika tes berjalan sangat cepat itu biasanya baik, meskipun Anda mungkin kemudian mempertimbangkan menjalankan tes dalam satu lingkaran dengan parameter yang berbeda. Ini membuatnya menjadi tes kinerja yang lebih baik dan juga meningkatkan cakupan pengujian Anda dari ruang parameter.
  5. Tulis beberapa tes tambahan yang secara khusus menargetkan kinerja, mis. Waktu transaksi end-to-end atau waktu untuk menyelesaikan 1.000 aturan aplikasi. Jika Anda memiliki persyaratan kinerja non-fungsional yang spesifik (mis., Waktu respons <300 ms), maka buat tes gagal jika terlalu lama.

Jika Anda terus melakukan semua ini, maka seiring waktu kinerja rata-rata basis kode Anda akan meningkat secara organik.

Mungkin juga untuk melacak waktu pengujian historis dan menggambar grafik kinerja dan melihat regresi dari waktu ke waktu dalam kinerja rata-rata. Saya tidak pernah repot dengan ini terutama karena agak sulit untuk memastikan Anda membandingkan suka dengan seperti saat Anda mengubah dan menambahkan tes baru, tetapi itu bisa menjadi latihan yang menarik jika kinerja cukup penting bagi Anda.


Saya suka teknik - pintar - howevre, ini adalah optimasi mikro dan akan meningkatkan ke minimum lokal - itu tidak akan memecahkan masalah arsitektur yang memungkinkan Anda untuk mencapai minimum global
jasonk

@jasonk - Anda memang benar. Meskipun saya akan menambahkan bahwa kadang-kadang dapat memberikan bukti yang Anda butuhkan untuk menunjukkan mengapa perubahan arsitektur tertentu dibenarkan .....
mikera

1

Jawaban oleh @dasblinkenlight menunjukkan masalah yang sangat umum, terutama dengan basis kode besar (dalam pengalaman saya). Mungkin ada masalah kinerja yang serius, tetapi tidak terlokalisasi . Jika Anda menjalankan profiler, tidak ada rutinitas yang membutuhkan waktu cukup lama, untuk menarik perhatian. (Asumsikan Anda melihat persentase waktu inklusif yang mencakup anak sapi. Jangan repot-repot dengan "waktu sendiri".)

Bahkan, dalam kasus itu, masalah sebenarnya tidak ditemukan oleh profiling, tetapi oleh keberuntungan.

Saya punya studi kasus, yang ada slide show PDF singkat , menggambarkan masalah ini secara rinci dan cara menanganinya. Poin dasarnya adalah, karena kodenya jauh lebih lambat dari yang seharusnya, itu berarti (menurut definisi) kelebihan waktu dihabiskan untuk melakukan sesuatu yang bisa dihapus.

Jika Anda melihat beberapa sampel waktu acak dari status program, Anda hanya akan melihatnya melakukan aktivitas yang dapat dilepas, karena persentase waktu yang diperlukan. Sangat mungkin aktivitas yang dapat dilepas tidak terbatas pada satu fungsi, atau bahkan banyak fungsi. Itu tidak melokalisasi seperti itu.

Itu bukan "hot spot".

Ini adalah deskripsi yang Anda buat dari apa yang Anda lihat, yang kebetulan benar dalam persentase besar waktu. Itu membuatnya mudah ditemukan, tetapi apakah mudah untuk memperbaikinya tergantung pada seberapa banyak penulisan ulang yang diperlukan.

(Ada kritik yang sering dibuat dari pendekatan ini, bahwa jumlah sampel terlalu kecil untuk validitas statistik. Itu dijawab pada slide 13 dari PDF. Secara singkat - ya, ada ketidakpastian yang tinggi dalam "pengukuran" penghematan potensial, tetapi 1) nilai yang diharapkan dari penghematan potensial itu pada dasarnya tidak terpengaruh, dan 2) ketika potensi penghematan $ x $ diterjemahkan ke dalam rasio kenaikan sebesar $ 1 / (1-x) $, itu sangat condong ke faktor-faktor (menguntungkan) yang tinggi.)


Terima kasih atas jawaban anda. Kami tidak percaya pada pengambilan sampel statistik dan menggunakan instrumentasi dengan valgrind. Ini memberi kami perkiraan yang baik untuk biaya "diri" dan "inklusif" dari sebagian besar barang yang kami lakukan.
Benjamin Bannier

@honk: Benar. Tetapi sayangnya, instrumentasi masih membawa ide bahwa masalah kinerja dilokalkan & demikian dapat ditemukan dengan mengukur fraksi waktu yang dihabiskan dalam rutinitas, dll. Anda tentu saja dapat menjalankan valgrind dll., Tetapi jika Anda ingin wawasan nyata ke dalam kinerja periksa slide show itu .
Mike Dunlavey
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.