Strategi optimalisasi kinerja dari upaya terakhir [ditutup]

609

Ada banyak pertanyaan kinerja di situs ini, tetapi terpikir oleh saya bahwa hampir semua sangat spesifik masalah dan cukup sempit. Dan hampir semua mengulangi saran untuk menghindari optimasi prematur.

Mari kita asumsikan:

  • kode sudah berfungsi dengan benar
  • algoritma yang dipilih sudah optimal untuk keadaan masalah
  • kode telah diukur, dan rutinitas yang menyinggung telah diisolasi
  • semua upaya untuk mengoptimalkan juga akan diukur untuk memastikan mereka tidak memperburuk masalah

Yang saya cari di sini adalah strategi dan trik untuk memeras hingga beberapa persen terakhir dalam algoritma kritis ketika tidak ada lagi yang bisa dilakukan selain apa pun yang diperlukan.

Idealnya, cobalah untuk membuat jawaban agnostik bahasa, dan tunjukkan segala kelemahan dari strategi yang disarankan di mana berlaku.

Saya akan menambahkan balasan dengan saran awal saya sendiri, dan berharap apa pun yang bisa dipikirkan komunitas Stack Overflow.

jerryjvl
sumber

Jawaban:

427

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 ++iatau 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.

Mike Dunlavey
sumber
3
Saya ingin membaca beberapa detail langkah-langkah yang Anda uraikan di atas. Apakah mungkin untuk memasukkan beberapa fragmen pengoptimalan rasa? (tanpa membuat pos terlalu lama?)
jerryjvl
8
... Saya juga menulis sebuah buku yang sekarang sudah tidak dicetak, jadi harganya sangat mahal di Amazon - "Membangun Aplikasi yang Lebih Baik" ISBN 0442017405. Pada dasarnya materi yang sama ada di bab pertama.
Mike Dunlavey
3
@ Mike Dunlavey, saya sarankan memberitahu Google bahwa Anda sudah dipindai. Mereka mungkin sudah memiliki perjanjian dengan siapa pun yang membeli penerbit Anda.
Thorbjørn Ravn Andersen
19
@ Thorbjørn: Hanya untuk menindaklanjuti, saya benar-benar terhubung dengan GoogleBooks, mengisi semua formulir, dan mengirimkan kepada mereka salinan cetak. Saya mendapat email kembali menanyakan apakah saya benar-benar memiliki hak cipta. Penerbit Van Nostrand Reinhold, yang dibeli oleh International Thompson, yang dibeli oleh Reuters, dan ketika saya mencoba menelepon atau mengirim email kepada mereka itu seperti lubang hitam. Jadi itu dalam limbo - saya belum punya energi untuk benar-benar mengejarnya.
Mike Dunlavey
5
Tautan Google Buku: books.google.dk/books?id=8A43E1UFs_YC
Thorbjørn Ravn Andersen
188

Saran:

  • Pra-perhitungan dan bukan perhitungan ulang : setiap loop atau panggilan berulang yang berisi perhitungan yang memiliki rentang input yang relatif terbatas, pertimbangkan untuk melakukan pencarian (array atau kamus) yang berisi hasil perhitungan untuk semua nilai dalam kisaran valid dari input. Kemudian gunakan pencarian sederhana di dalam algoritma sebagai gantinya.
    Kelemahan : jika beberapa nilai pra-komputasi benar-benar digunakan, hal ini dapat memperburuk keadaan, juga pencarian dapat mengambil memori yang signifikan.
  • Jangan gunakan metode pustaka : sebagian besar pustaka harus ditulis untuk beroperasi dengan benar di bawah skenario yang luas, dan melakukan pemeriksaan nol pada parameter, dll. Dengan menerapkan kembali metode, Anda mungkin dapat menghapus banyak logika yang tidak berlaku dalam keadaan persis seperti yang Anda gunakan.
    Kelemahan : menulis kode tambahan berarti lebih banyak area permukaan untuk bug.
  • Gunakan metode perpustakaan : untuk membantah diri saya sendiri, perpustakaan bahasa ditulis oleh orang-orang yang jauh lebih pintar daripada Anda atau saya; kemungkinan besar mereka melakukannya lebih baik dan lebih cepat. Jangan menerapkannya sendiri kecuali Anda benar-benar dapat membuatnya lebih cepat (yaitu: selalu ukur!)
  • Cheat : dalam beberapa kasus meskipun perhitungan yang tepat mungkin ada untuk masalah Anda, Anda mungkin tidak perlu 'tepat', kadang-kadang perkiraan mungkin 'cukup baik' dan jauh lebih cepat dalam kesepakatan. Tanyakan pada diri Anda, apakah benar-benar penting jika jawabannya keluar 1%? 5%? bahkan 10%?
    Kelemahan : Ya ... jawabannya tidak tepat.
jerigen
sumber
32
Pra-perhitungan tidak selalu membantu, dan kadang-kadang bahkan bisa merugikan - jika tabel pencarian Anda terlalu besar, itu bisa membunuh kinerja cache Anda.
Adam Rosenfield
37
Kecurangan sering kali bisa menjadi kemenangan. Saya memiliki proses koreksi warna yang pada intinya adalah 3-vektor bertitik dengan matriks 3x3. CPU memiliki matriks multiply dalam perangkat keras yang meninggalkan beberapa istilah lintas dan berjalan sangat cepat dibandingkan dengan semua cara lain untuk melakukannya, tetapi hanya mendukung matriks 4x4 dan 4-vektor pelampung. Mengubah kode untuk membawa slot kosong ekstra dan mengubah perhitungan menjadi titik mengambang dari titik tetap memungkinkan untuk hasil yang sedikit kurang akurat tetapi jauh lebih cepat.
RBerteig
6
Curangnya adalah dengan menggunakan perkalian matriks yang meninggalkan beberapa produk dalam, sehingga memungkinkan untuk diterapkan dalam mikrokode untuk instruksi CPU tunggal yang diselesaikan lebih cepat daripada urutan setara dari instruksi individual. Ini curang karena tidak mendapatkan jawaban yang "benar", hanya sebuah jawaban yang "cukup benar".
RBerteig
6
@RBerteig: cukup "benar" adalah peluang untuk optimasi yang kebanyakan orang lewatkan dalam pengalaman saya.
Martin Thompson
5
Anda tidak selalu dapat berasumsi bahwa setiap orang lebih cerdas daripada Anda. Pada akhirnya kita semua adalah para profesional. Anda dapat mengasumsikan, bahwa perpustakaan tertentu yang Anda gunakan ada dan telah mencapai lingkungan Anda karena kualitasnya, oleh karena itu penulisan perpustakaan ini harus sangat teliti, Anda tidak dapat melakukannya juga hanya karena Anda tidak berspesialisasi dalam hal itu. bidang, dan Anda tidak menginvestasikan waktu yang sama di dalamnya. Bukan karena kamu kurang pintar. Ayolah.
v.oddou
164

Ketika Anda tidak dapat meningkatkan kinerja lagi - lihat apakah Anda dapat meningkatkan kinerja yang dirasakan sebagai gantinya.

Anda mungkin tidak dapat membuat algoritma fooCalc Anda lebih cepat, tetapi seringkali ada cara untuk membuat aplikasi Anda tampak lebih responsif terhadap pengguna.

Beberapa contoh:

  • mengantisipasi apa yang akan diminta pengguna dan mulai mengerjakannya sebelum itu
  • menampilkan hasil saat masuk, alih-alih sekaligus di akhir
  • Indikator kemajuan yang akurat

Ini tidak akan membuat program Anda lebih cepat, tetapi mungkin membuat pengguna Anda lebih bahagia dengan kecepatan yang Anda miliki.

kenj0418
sumber
27
Bilah kemajuan yang mempercepat pada akhirnya mungkin dianggap lebih cepat daripada yang benar-benar akurat. Dalam "Rethinking the Progress Bar" (2007) Harrison, Amento, Kuznetsov dan Bell menguji beberapa jenis bar pada sekelompok pengguna serta mendiskusikan beberapa cara untuk mengatur ulang operasi sehingga kemajuan dapat dirasakan lebih cepat.
Emil Vikström
9
naxa, sebagian besar progress bar palsu karena memprediksi beberapa langkah yang berbeda dari aliran menjadi persentase tunggal sulit atau kadang-kadang tidak mungkin. Lihat saja semua bar yang macet 99% :-(
Emil Vikström
138

Saya menghabiskan sebagian besar hidup saya di tempat ini saja. Langkah luasnya adalah menjalankan profiler Anda dan merekamnya:

  • Tembolok merindukan . Cache data adalah sumber nomor 1 di sebagian besar program. Tingkatkan cache hit rate dengan mengatur ulang struktur data yang menyinggung agar memiliki lokalitas yang lebih baik; kemas struktur dan tipe numerik ke bawah untuk menghilangkan byte yang terbuang (dan karena itu cache yang terbuang mengambil); prefetch data sedapat mungkin untuk mengurangi warung.
  • Load-hit-stores . Asumsi kompiler tentang aliasing pointer, dan kasus-kasus di mana data dipindahkan antara set register yang terputus melalui memori, dapat menyebabkan perilaku patologis tertentu yang menyebabkan seluruh pipa CPU menghapus pada op beban. Temukan tempat-tempat mengapung, vektor, dan int saling melemparkan dan menghilangkannya. Gunakan __restrictsecara bebas untuk menjanjikan kompilator tentang aliasing.
  • Operasi mikro . Sebagian besar prosesor memiliki beberapa operasi yang tidak dapat dilakukan pipelined, tetapi menjalankan subroutine kecil yang disimpan dalam ROM. Contoh pada PowerPC adalah integer multiply, divide, dan shift-by-variable-jumlah. Masalahnya adalah bahwa seluruh pipa berhenti mati saat operasi ini sedang dijalankan. Cobalah untuk menghilangkan penggunaan operasi ini atau setidaknya memecahnya menjadi operasi pipeline konstituen mereka sehingga Anda bisa mendapatkan manfaat dari pengiriman superscalar pada apa pun yang dilakukan program Anda.
  • Cabang salah duga . Ini juga mengosongkan pipa. Temukan kasus di mana CPU menghabiskan banyak waktu mengisi ulang pipa setelah cabang, dan gunakan petunjuk cabang jika tersedia untuk membuatnya memprediksi dengan lebih sering. Atau lebih baik lagi, ganti cabang dengan gerakan kondisional sedapat mungkin, terutama setelah operasi titik apung karena pipa mereka biasanya lebih dalam dan membaca bendera kondisi setelah fcmp dapat menyebabkan kemacetan.
  • Operasi floating-point berurutan . Buat SIMD ini.

Dan satu hal lagi yang ingin saya lakukan:

  • Tetapkan kompiler Anda ke daftar unit keluaran dan lihat apa yang dipancarkannya untuk fungsi hotspot dalam kode Anda. Semua optimisasi cerdas yang "dapat dikompilasi oleh kompiler yang baik untuk Anda secara otomatis"? Kemungkinan kompiler Anda yang sebenarnya tidak melakukannya. Saya telah melihat GCC benar-benar mengeluarkan kode WTF.
Crashworks
sumber
8
Saya kebanyakan menggunakan Intel VTune dan PIX. Tidak tahu apakah mereka dapat beradaptasi dengan C #, tetapi benar-benar setelah Anda mendapatkan lapisan abstraksi JIT sebagian besar optimasi ini berada di luar jangkauan Anda, kecuali untuk meningkatkan lokalitas cache dan mungkin menghindari beberapa cabang.
Crashworks
6
Meski begitu, memeriksa output pasca-JIT dapat membantu mencari tahu apakah ada konstruksi yang tidak optimal dengan baik melalui tahap JIT ... penyelidikan tidak akan pernah merugikan, bahkan jika ternyata jalan buntu.
jerryjvl
5
Saya pikir banyak orang, termasuk saya, akan tertarik dengan "perakitan wtf" yang diproduksi oleh gcc. Anda terdengar seperti pekerjaan yang sangat menarik :)
BlueRaja - Danny Pflughoeft
1
Examples on the PowerPC ...<- Yaitu, beberapa implementasi PowerPC. PowerPC adalah ISA, bukan CPU.
Billy ONeal
1
@BillyONeal Bahkan pada perangkat keras x86 modern, imul dapat menghentikan pipa; lihat "Manual Referensi Optimalisasi Arsitektur Arsitektur Intel® 64 dan IA-32" §13.3.2.3: "Instruksi pengganda integer membutuhkan beberapa siklus untuk dieksekusi. Mereka disalin sedemikian rupa sehingga instruksi pengganda integer dan instruksi laten panjang lainnya dapat membuat kemajuan maju dalam tahap eksekusi. Namun, instruksi pengganda integer akan memblokir instruksi integer siklus tunggal lainnya agar tidak dikeluarkan karena persyaratan pesanan program. " Itu sebabnya biasanya lebih baik menggunakan ukuran dan array yang selaras kata lea.
Crashworks
78

Lempar lebih banyak perangkat keras padanya!

sisve
sumber
30
lebih banyak perangkat keras tidak selalu menjadi pilihan saat Anda memiliki perangkat lunak yang diharapkan berjalan pada perangkat keras yang sudah ada di lapangan.
Doug T.
76
Bukan jawaban yang sangat membantu untuk seseorang yang membuat perangkat lunak konsumen: pelanggan tidak akan mau mendengar Anda berkata, "beli komputer yang lebih cepat." Terutama jika Anda sedang menulis perangkat lunak untuk menargetkan sesuatu seperti konsol permainan video.
Crashworks
19
@ Crashworks, atau dalam hal ini, sistem tertanam. Ketika fitur terakhir adalah akhirnya di dan batch pertama dari papan sudah berputar bukan saat untuk menemukan bahwa Anda harus menggunakan CPU yang lebih cepat di tempat pertama ...
RBerteig
71
Saya pernah melakukan debug program yang memiliki kebocoran memori besar - ukuran VM-nya tumbuh sekitar 1Mb per jam. Seorang kolega bercanda bahwa semua yang perlu saya lakukan adalah menambah memori pada tingkat yang konstan . :)
j_random_hacker
9
Lebih banyak perangkat keras: ah ya garis hidup pengembang biasa-biasa saja. Saya tidak tahu berapa kali saya mendengar "tambahkan mesin lain dan gandakan kapasitasnya!"
Olof Forshell
58

Lebih banyak saran:

  • Hindari I / O : I / O apa pun (disk, jaringan, port, dll.) Akan selalu jauh lebih lambat daripada kode apa pun yang melakukan perhitungan, jadi singkirkan I / O yang tidak Anda perlukan secara ketat.

  • Pindahkan I / O di muka : Muat semua data yang Anda perlukan untuk perhitungan di muka, sehingga Anda tidak perlu mengulangi I / O dalam inti dari algoritma kritis (dan mungkin akibatnya diulang disk mencari, ketika memuat semua data dalam satu pukulan dapat menghindari pencarian).

  • Keterlambatan I / O : Jangan menuliskan hasil Anda sampai perhitungan selesai, simpan hasilnya dalam struktur data dan kemudian buang hasilnya dalam sekali jalan saat kerja keras selesai.

  • I / O Berulir : Untuk yang cukup berani, gabungkan 'I / O di muka' atau 'Tunda I / O' dengan perhitungan aktual dengan memindahkan pemuatan ke thread paralel, sehingga saat Anda memuat lebih banyak data, Anda dapat bekerja pada perhitungan pada data yang sudah Anda miliki, atau saat Anda menghitung kumpulan data berikutnya Anda dapat secara bersamaan menuliskan hasil dari kumpulan terakhir.

Peter Mortensen
sumber
3
Perhatikan bahwa "memindahkan IO ke utas paralel" harus dilakukan sebagai IO asinkron pada banyak platform (misalnya Windows NT).
Billy ONeal
2
I / O memang titik kritis, karena lambat dan memiliki latensi besar, dan Anda bisa lebih cepat dengan saran ini, tetapi masih cacat secara mendasar: Poinnya adalah latensi (yang harus disembunyikan) dan overhead syscall ( yang harus dikurangi dengan mengurangi jumlah panggilan I / O). Saran terbaik adalah: gunakan mmap()untuk input, lakukan madvise()panggilan yang sesuai dan gunakan aio_write()untuk menulis potongan besar output (= beberapa MiB).
cmaster - mengembalikan monica
1
Opsi terakhir ini cukup mudah diterapkan di Jawa, khususnya. Ini memberi peningkatan kinerja BESAR untuk aplikasi yang saya tulis. Poin penting lainnya (lebih dari memindahkan I / O di muka) adalah membuatnya SEQUENTIAL dan I-O blok besar. Banyak bacaan kecil jauh lebih mahal daripada 1 besar, karena waktu mencari disk.
BobMcGee
Pada satu titik saya curang dalam menghindari I / O, dengan hanya memindahkan sementara semua file ke disk RAM sebelum perhitungan dan memindahkannya kembali sesudahnya. Ini kotor, tetapi mungkin berguna dalam situasi di mana Anda tidak mengontrol logika yang membuat panggilan I / O.
MD
48

Karena banyak masalah kinerja melibatkan masalah basis data, saya akan memberi Anda beberapa hal spesifik untuk dilihat ketika menyetel kueri dan prosedur tersimpan.

Hindari kursor di sebagian besar basis data. Hindari perulangan juga. Sebagian besar waktu, akses data harus berbasis set, bukan catatan dengan pemrosesan catatan. Ini termasuk tidak menggunakan kembali prosedur penyimpanan rekaman tunggal ketika Anda ingin memasukkan 1.000.000 catatan sekaligus.

Jangan pernah menggunakan pilih *, hanya mengembalikan bidang yang sebenarnya Anda butuhkan. Ini terutama benar jika ada gabungan karena bidang gabungan akan diulang dan dengan demikian menyebabkan beban yang tidak perlu pada server dan jaringan.

Hindari penggunaan subquery yang berhubungan. Gunakan gabungan (termasuk gabungan ke tabel turunan jika memungkinkan) (Saya tahu ini benar untuk Microsoft SQL Server, tetapi uji saran saat menggunakan backend differnt).

Indeks, indeks, indeks. Dan perbarui statistik tersebut jika berlaku untuk basis data Anda.

Buat kueri menjadi lebih murah . Berarti menghindari hal-hal yang membuat tidak mungkin untuk menggunakan indeks seperti menggunakan wildcard dalam karakter pertama klausa suka atau fungsi dalam bergabung atau sebagai bagian kiri pernyataan di mana.

Gunakan tipe data yang benar. Lebih cepat melakukan matematika tanggal pada bidang tanggal daripada harus mencoba mengubah string tipe data ke tipe tanggal, kemudian melakukan perhitungan.

Jangan sekali-kali membuat loop apa pun menjadi pemicu!

Sebagian besar database memiliki cara untuk memeriksa bagaimana eksekusi permintaan akan dilakukan. Dalam Microsoft SQL Server ini disebut rencana eksekusi. Periksa yang pertama untuk melihat di mana letak masalah.

Pertimbangkan seberapa sering kueri berjalan serta berapa lama untuk menjalankan saat menentukan apa yang perlu dioptimalkan. Kadang-kadang Anda bisa mendapatkan lebih banyak kinerja dari sedikit tweak ke kueri yang berjalan jutaan kali sehari daripada Anda bisa menghapus waktu dari permintaan long_running yang hanya berjalan sebulan sekali.

Gunakan semacam alat profiler untuk mengetahui apa yang sebenarnya dikirim ke dan dari database. Saya dapat mengingat suatu saat di masa lalu di mana kami tidak dapat mengetahui mengapa halaman tersebut sangat lambat dimuat ketika prosedur yang tersimpan cepat dan menemukan melalui profil bahwa halaman web meminta permintaan berkali-kali alih-alih sekali.

Profiler juga akan membantu Anda menemukan siapa yang memblokir siapa. Beberapa kueri yang dijalankan dengan cepat saat berjalan sendiri mungkin menjadi sangat lambat karena kunci dari kueri lain.

HLGEM
sumber
29

Satu-satunya faktor pembatas terpenting saat ini adalah keterbatasan memori bandwitdh . Multicores hanya memperburuk ini, karena bandwidth dibagi antara core. Selain itu, area chip terbatas yang ditujukan untuk mengimplementasikan cache juga dibagi di antara inti dan utas, semakin memperburuk masalah ini. Akhirnya, pensinyalan antar-chip yang diperlukan untuk menjaga agar cache berbeda tetap koheren juga meningkat dengan peningkatan jumlah core. Ini juga menambah penalti.

Ini adalah efek yang perlu Anda kelola. Terkadang melalui pengelolaan mikro kode Anda, tetapi kadang-kadang melalui pertimbangan dan refactoring yang cermat.

Banyak komentar yang menyebutkan kode ramah cache. Setidaknya ada dua rasa berbeda dari ini:

  • Hindari pengambilan memori latensi.
  • Tekanan bus memori lebih rendah (bandwidth).

Masalah pertama secara khusus berkaitan dengan membuat pola akses data Anda lebih teratur, memungkinkan prefetcher perangkat keras untuk bekerja secara efisien. Hindari alokasi memori dinamis yang menyebarkan objek data Anda di dalam memori. Gunakan wadah linier alih-alih daftar terkait, hash, dan pohon.

Masalah kedua berkaitan dengan meningkatkan penggunaan kembali data. Ubah algoritma Anda untuk bekerja pada himpunan bagian dari data Anda yang cocok dengan cache yang tersedia, dan gunakan kembali data itu sebanyak mungkin saat masih dalam cache.

Pengepakan data lebih ketat dan memastikan Anda menggunakan semua data dalam garis cache di loop panas, akan membantu menghindari efek lain ini, dan memungkinkan pemasangan data yang lebih berguna dalam cache.

Mats N
sumber
25
  • Perangkat keras apa yang Anda jalankan? Bisakah Anda menggunakan pengoptimalan khusus platform (seperti vektorisasi)?
  • Bisakah Anda mendapatkan kompiler yang lebih baik? Misalnya beralih dari GCC ke Intel?
  • Bisakah Anda membuat algoritma Anda berjalan secara paralel?
  • Bisakah Anda mengurangi kesalahan cache dengan mengatur ulang data?
  • Bisakah Anda menonaktifkan pernyataan?
  • Mikro-optimalkan untuk kompiler dan platform Anda. Dalam gaya, "pada if / else, letakkan pernyataan paling umum terlebih dahulu"
Johan Kotlinski
sumber
4
Harus "beralih dari GCC ke LLVM" :)
Zifre
4
Bisakah Anda membuat algoritma Anda berjalan secara paralel? - kebalikannya juga berlaku
justin
4
Benar bahwa, mengurangi jumlah utas dapat menjadi optimasi yang sama baiknya
Johan Kotlinski
re: micro-optimizing: jika Anda memeriksa output asm kompiler, Anda sering dapat mengubah sumber untuk menahannya agar menghasilkan asm yang lebih baik. Lihat Mengapa kode C ++ ini lebih cepat daripada rakitan tulisan tangan saya untuk menguji dugaan Collatz? untuk lebih lanjut tentang membantu atau mengalahkan kompiler pada x86 modern.
Peter Cordes
17

Meskipun saya menyukai jawaban Mike Dunlavey, sebenarnya itu adalah jawaban yang bagus dengan contoh yang mendukung, saya pikir itu bisa diungkapkan dengan sangat sederhana sebagai berikut:

Cari tahu apa yang membutuhkan jumlah waktu terbesar pertama, dan pahami alasannya.

Ini adalah proses identifikasi babi waktu yang membantu Anda memahami di mana Anda harus memperbaiki algoritma Anda. Ini adalah satu-satunya jawaban agnostik bahasa yang mencakup semua yang dapat saya temukan untuk masalah yang sudah seharusnya dioptimalkan sepenuhnya. Juga anggap Anda ingin arsitektur independen dalam pencarian Anda untuk kecepatan.

Jadi, sementara algoritme mungkin dioptimalkan, implementasinya mungkin tidak. Identifikasi memungkinkan Anda untuk mengetahui bagian mana: algoritma atau implementasi. Jadi, siapa pun yang paling banyak menghabiskan waktu adalah kandidat utama Anda untuk ditinjau. Tetapi karena Anda mengatakan Anda ingin memeras beberapa% terakhir, Anda mungkin ingin juga memeriksa bagian yang lebih rendah, bagian yang Anda belum memeriksanya sedekat itu pada awalnya.

Terakhir sedikit coba-coba dengan angka kinerja tentang cara berbeda untuk mengimplementasikan solusi yang sama, atau algoritma yang berpotensi berbeda, dapat membawa wawasan yang membantu mengidentifikasi pemboros waktu dan penghemat waktu.

HPH, silakan pindahkan.

asoundmove
sumber
16

Anda mungkin harus mempertimbangkan "perspektif Google", yaitu menentukan bagaimana aplikasi Anda dapat menjadi sebagian besar diparalelkan dan bersamaan, yang pada akhirnya juga akan berarti untuk mendistribusikan aplikasi Anda di berbagai mesin dan jaringan yang berbeda, sehingga idealnya dapat skala hampir linier dengan perangkat keras yang Anda lemparkan padanya.

Di sisi lain, orang-orang Google juga dikenal karena melemparkan banyak tenaga kerja dan sumber daya untuk menyelesaikan beberapa masalah dalam proyek, alat dan infrastruktur yang mereka gunakan, seperti misalnya seluruh optimasi program untuk gcc dengan memiliki tim insinyur yang berdedikasi meretas internal gcc untuk mempersiapkannya untuk skenario kasus penggunaan khas Google.

Demikian pula, membuat profil suatu aplikasi tidak lagi hanya sekadar membuat profil kode program, tetapi juga semua sistem dan infrastruktur di sekitarnya (bayangkan jaringan, switch, server, array RAID) untuk mengidentifikasi redudansi dan potensi optimisasi dari sudut pandang sistem.

tidak ada
sumber
15
  • Rutin inline (menghilangkan panggilan / kembali dan parameter mendorong)
  • Coba hilangkan tes / switch dengan table look up (jika lebih cepat)
  • Buka gulungannya (perangkat Duff) ke titik di mana mereka pas dengan cache CPU
  • Lokalkan akses memori agar tidak meledakkan cache Anda
  • Pelokalkan perhitungan terkait jika pengoptimal belum melakukan itu
  • Hilangkan invarian lingkaran jika pengoptimal belum melakukan itu
alas tiang
sumber
2
Perangkat IIRC Duff sangat jarang lebih cepat. Hanya ketika op sangat pendek (seperti ekspresi matematika kecil)
BCS
12
  • Ketika Anda sampai pada titik bahwa Anda menggunakan algoritma yang efisien itu pertanyaan tentang apa yang Anda butuhkan lebih banyak kecepatan atau memori . Gunakan caching untuk "membayar" dalam memori untuk kecepatan lebih tinggi atau menggunakan perhitungan untuk mengurangi jejak memori.
  • Jika mungkin (dan lebih hemat biaya) melemparkan perangkat keras pada masalah - CPU lebih cepat, lebih banyak memori atau HD dapat memecahkan masalah lebih cepat kemudian mencoba untuk kode itu.
  • Gunakan paralelisasi jika mungkin - jalankan bagian dari kode pada banyak utas.
  • Gunakan alat yang tepat untuk pekerjaan itu . beberapa bahasa pemrograman membuat kode lebih efisien, menggunakan kode terkelola (yaitu Java / .NET) mempercepat pengembangan tetapi bahasa pemrograman asli membuat kode berjalan lebih cepat.
  • Mikro mengoptimalkan . Hanya berlaku jika Anda dapat menggunakan perakitan yang dioptimalkan untuk mempercepat potongan kecil kode, menggunakan optimasi SSE / vektor di tempat yang tepat dapat sangat meningkatkan kinerja.
Dror Helper
sumber
12

Memecah dan menaklukkan

Jika dataset yang sedang diproses terlalu besar, lakukan perulangan. Jika Anda telah melakukan kode dengan benar, implementasi harus mudah. Jika Anda memiliki program monolitik, sekarang Anda tahu lebih baik.

MPelletier
sumber
9
1 untuk bunyi "memukul" flyswatter yang saya dengar saat membaca kalimat terakhir.
Bryan Boettcher
11

Pertama-tama, seperti disebutkan dalam beberapa jawaban sebelumnya, pelajari apa yang menggigit kinerja Anda - apakah itu memori atau prosesor atau jaringan atau database atau sesuatu yang lain. Tergantung pada ...

  • ... jika itu ingatan - temukan salah satu buku yang sudah lama ditulis oleh Knuth, salah satu seri "The Art of Computer Programming". Kemungkinan besar ini tentang penyortiran dan pencarian - jika ingatan saya salah maka Anda harus mencari tahu di mana ia berbicara tentang cara menangani penyimpanan data pita lambat. Mentransformasi pasangan memori / tape secara mental menjadi pasangan cache / memori utama (atau pasangan cache L1 / L2) secara berurutan. Pelajari semua trik yang ia jelaskan - jika Anda tidak menemukan sesuatu yang menyelesaikan masalah Anda, maka sewalah ilmuwan komputer profesional untuk melakukan penelitian profesional. Jika masalah memori Anda adalah kebetulan dengan FFT (cache meleset pada indeks bit-reversed ketika melakukan kupu-kupu radix-2) maka jangan mempekerjakan seorang ilmuwan - sebagai gantinya, optimalkan secara manual melewati satu-per-satu sampai Anda apakah menang atau menemui jalan buntu. Anda menyebutkanmemeras hingga beberapa persen terakhir kan? Jika sedikit, Anda kemungkinan besar akan menang.

  • ... jika prosesor - beralih ke bahasa assembly. Mempelajari spesifikasi prosesor - apa yang perlu kutu , VLIW, SIMD. Panggilan fungsi kemungkinan besar adalah tick-eaters yang dapat diganti. Pelajari transformasi loop - saluran pipa, buka gulungannya. Penggandaan dan pembagian mungkin bisa diganti / diinterpolasi dengan pergeseran bit (dikalikan dengan bilangan bulat kecil mungkin bisa diganti dengan penambahan). Cobalah trik dengan data yang lebih pendek - jika Anda beruntung satu instruksi dengan 64 bit mungkin berubah tergantikan dengan dua pada 32 atau bahkan 4 pada 16 atau 8 pada 8 bit. Coba juga lebih lamadata - mis. perhitungan float Anda mungkin menjadi lebih lambat daripada yang dua kali lipat pada prosesor tertentu. Jika Anda memiliki barang trigonometri, lawanlah dengan tabel yang sudah dihitung sebelumnya; juga perlu diingat bahwa nilai sinus yang kecil dapat diganti dengan nilai tersebut jika kehilangan presisi berada dalam batas yang diizinkan.

  • ... jika itu jaringan - pikirkan untuk mengompresi data yang Anda lewati. Ganti transfer XML dengan biner. Protokol penelitian. Coba UDP alih-alih TCP jika Anda entah bagaimana dapat menangani kehilangan data.

  • ... jika itu adalah basis data, pergilah ke forum basis data mana saja dan mintalah saran. Kotak data dalam memori, mengoptimalkan rencana permintaan, dll. Dll.

HTH :)

agas
sumber
9

Caching! Cara murah (dalam upaya programmer) untuk membuat hampir semua hal lebih cepat adalah dengan menambahkan lapisan abstraksi caching ke area pergerakan data program Anda. Baik itu I / O atau hanya lewat / kreasi objek atau struktur. Seringkali mudah untuk menambahkan cache ke kelas pabrik dan pembaca / penulis.

Terkadang cache tidak akan banyak membantu Anda, tetapi ini adalah metode yang mudah untuk menambahkan semua caching dan menonaktifkannya di tempat yang tidak membantu. Saya sering menemukan ini untuk mendapatkan kinerja besar tanpa harus menganalisa kode secara mikro.

Killroy
sumber
8

Saya pikir ini sudah dikatakan dengan cara yang berbeda. Tetapi ketika Anda berurusan dengan algoritma intensif prosesor, Anda harus menyederhanakan segala sesuatu di dalam loop paling dalam dengan mengorbankan yang lainnya.

Itu mungkin tampak jelas bagi sebagian orang, tetapi itu adalah sesuatu yang saya coba fokuskan terlepas dari bahasa yang saya gunakan. Jika Anda berurusan dengan loop bersarang, misalnya, dan Anda menemukan peluang untuk menurunkan beberapa kode, Anda dalam beberapa kasus dapat secara drastis mempercepat kode Anda. Sebagai contoh lain, ada beberapa hal kecil yang perlu dipikirkan seperti bekerja dengan integer alih-alih variabel floating point kapan pun Anda bisa, dan menggunakan perkalian alih-alih pembagian kapan pun Anda bisa. Sekali lagi, ini adalah hal-hal yang harus dipertimbangkan untuk loop paling dalam Anda.

Kadang-kadang Anda mungkin menemukan manfaat melakukan operasi matematika Anda pada bilangan bulat di dalam lingkaran dalam, dan kemudian turunkan ke variabel floating point yang dapat Anda kerjakan setelahnya. Itu adalah contoh pengorbanan kecepatan di satu bagian untuk meningkatkan kecepatan di bagian lain, tetapi dalam beberapa kasus pembayarannya sepadan.

Steve Wortham
sumber
8

Saya telah menghabiskan beberapa waktu bekerja untuk mengoptimalkan sistem bisnis klien / server yang beroperasi melalui jaringan bandwidth rendah dan latensi panjang (misalnya satelit, jarak jauh, lepas pantai), dan mampu mencapai beberapa peningkatan kinerja yang dramatis dengan proses yang cukup berulang.

  • Ukur : Mulailah dengan memahami kapasitas dan topologi jaringan yang mendasarinya. Berbicara dengan orang-orang jaringan yang relevan dalam bisnis, dan menggunakan alat-alat dasar seperti ping dan traceroute untuk menetapkan (minimal) latensi jaringan dari setiap lokasi klien, selama periode operasional yang khas. Selanjutnya, lakukan pengukuran waktu akurat atas fungsi pengguna akhir tertentu yang menampilkan gejala bermasalah. Catat semua pengukuran ini, beserta lokasi, tanggal, dan waktunya. Pertimbangkan untuk membangun fungsionalitas "pengujian kinerja jaringan" pengguna akhir ke dalam aplikasi klien Anda, memungkinkan pengguna listrik Anda untuk berpartisipasi dalam proses peningkatan; memberdayakan mereka seperti ini dapat memiliki dampak psikologis yang sangat besar ketika Anda berurusan dengan pengguna yang frustrasi dengan sistem yang berkinerja buruk.

  • Analisis : Menggunakan setiap dan semua metode logging yang tersedia untuk menetapkan data apa yang sedang dikirim dan diterima selama pelaksanaan operasi yang terkena dampak. Idealnya, aplikasi Anda dapat menangkap data yang dikirim dan diterima oleh klien dan server. Jika ini termasuk cap waktu, bahkan lebih baik. Jika pencatatan yang memadai tidak tersedia (mis. Sistem tertutup, atau ketidakmampuan untuk menyebarkan modifikasi ke lingkungan produksi), gunakan sniffer jaringan dan pastikan Anda benar-benar memahami apa yang terjadi di tingkat jaringan.

  • Cache : Cari kasus di mana data statis atau jarang berubah sedang dikirim berulang-ulang dan pertimbangkan strategi cache yang tepat. Contoh umum termasuk nilai "daftar pilih" atau "entitas referensi" lainnya, yang bisa sangat besar di beberapa aplikasi bisnis. Dalam banyak kasus, pengguna dapat menerima bahwa mereka harus memulai ulang atau menyegarkan aplikasi untuk memperbarui data yang jarang diperbarui, terutama jika dapat mencukur waktu yang signifikan dari tampilan elemen antarmuka pengguna yang umum digunakan. Pastikan Anda memahami perilaku sebenarnya dari elemen-elemen caching yang sudah digunakan - banyak metode caching umum (misalnya HTTP ETag) masih memerlukan round-trip jaringan untuk memastikan konsistensi, dan di mana latensi jaringan mahal, Anda mungkin dapat menghindarinya sama sekali dengan pendekatan caching yang berbeda.

  • Paralelise : Cari transaksi sekuensial yang tidak perlu secara logis dikeluarkan secara berurutan, dan ulang sistem untuk menerbitkannya secara paralel. Saya berurusan dengan satu kasus di mana permintaan end-to-end memiliki keterlambatan jaringan yang melekat ~ 2s, yang bukan masalah untuk satu transaksi, tetapi ketika 6 perjalanan 2s berurutan diperlukan sebelum pengguna mendapatkan kembali kendali atas aplikasi klien , itu menjadi sumber frustrasi besar. Menemukan bahwa transaksi ini sebenarnya independen memungkinkan mereka untuk dieksekusi secara paralel, mengurangi keterlambatan pengguna akhir menjadi sangat dekat dengan biaya satu kali perjalanan.

  • Combine : Jika permintaan berurutan harus dijalankan secara berurutan, cari peluang untuk menggabungkannya menjadi satu permintaan yang lebih komprehensif. Contoh umum termasuk pembuatan entitas baru, diikuti oleh permintaan untuk menghubungkan entitas tersebut dengan entitas lain yang ada.

  • Kompres : Carilah peluang untuk meningkatkan kompresi muatan, baik dengan mengganti bentuk teks dengan bentuk biner, atau menggunakan teknologi kompresi yang sebenarnya. Banyak tumpukan teknologi modern (yaitu dalam satu dekade) mendukung hal ini hampir secara transparan, jadi pastikan itu dikonfigurasi. Saya sering terkejut dengan dampak signifikan dari kompresi di mana tampak jelas bahwa masalahnya pada dasarnya laten daripada bandwidth, menemukan setelah fakta bahwa itu memungkinkan transaksi untuk masuk ke dalam satu paket atau sebaliknya menghindari kehilangan paket dan karenanya memiliki ukuran yang lebih besar. berdampak pada kinerja.

  • Ulangi : Kembali ke awal dan ukur kembali operasi Anda (di lokasi dan waktu yang sama) dengan perbaikan di tempat, catat dan laporkan hasil Anda. Seperti halnya semua pengoptimalan, beberapa masalah mungkin telah dipecahkan dengan mengekspos masalah lain yang kini mendominasi.

Pada langkah-langkah di atas, saya fokus pada proses optimasi terkait aplikasi, tetapi tentu saja Anda harus memastikan jaringan yang mendasarinya dikonfigurasi dengan cara yang paling efisien untuk mendukung aplikasi Anda juga. Libatkan spesialis jaringan dalam bisnis dan tentukan apakah mereka dapat menerapkan peningkatan kapasitas, QoS, kompresi jaringan, atau teknik lain untuk mengatasi masalah tersebut. Biasanya, mereka tidak akan memahami kebutuhan aplikasi Anda, jadi penting Anda dilengkapi (setelah langkah Analisis) untuk mendiskusikannya dengan mereka, dan juga untuk membuat kasus bisnis untuk setiap biaya yang Anda akan meminta mereka untuk menanggung . Saya mengalami kasus di mana konfigurasi jaringan yang salah menyebabkan data aplikasi dikirim melalui tautan satelit lambat daripada tautan darat, hanya karena menggunakan port TCP yang tidak "dikenal" oleh spesialis jaringan; jelas memperbaiki masalah seperti ini dapat memiliki dampak dramatis pada kinerja, tanpa kode perangkat lunak atau perubahan konfigurasi yang diperlukan sama sekali.

Menepuk
sumber
7

Sangat sulit untuk memberikan jawaban umum untuk pertanyaan ini. Itu sangat tergantung pada domain masalah Anda dan implementasi teknis. Teknik umum yang cukup netral bahasa: Identifikasi hotspot kode yang tidak dapat dihilangkan, dan optimalkan kode assembler.

dschwarz
sumber
7

Beberapa% terakhir sangat tergantung pada CPU dan aplikasi ....

  • arsitektur cache berbeda, beberapa chip memiliki RAM on-chip yang dapat Anda petakan secara langsung, ARM (terkadang) memiliki unit vektor, SH4 adalah matriks opcode yang berguna. Apakah ada GPU - mungkin shader adalah cara untuk pergi. TMS320 sangat sensitif terhadap cabang dalam loop (jadi pisahkan loop dan pindah kondisi ke luar jika memungkinkan).

Daftarnya terus .... Tapi hal-hal seperti ini adalah pilihan terakhir ...

Bangun untuk x86, dan jalankan Valgrind / Cachegrind terhadap kode untuk profil kinerja yang tepat. Atau CCStudio dari Texas Instruments memiliki profiler yang manis. Maka Anda akan benar-benar tahu di mana harus fokus ...

Peter Mortensen
sumber
7

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

Untuk proyek non-offline, sembari memiliki perangkat lunak dan perangkat keras terbaik, jika throughput Anda lemah, maka garis tipis itu akan memeras data dan memberi Anda penundaan, meskipun dalam milidetik ... tetapi jika Anda berbicara tentang tetes terakhir , itu adalah beberapa tetes yang didapat, 24/7 untuk setiap paket yang dikirim atau diterima.

Sam
sumber
7

Tidak sedalam atau serumit jawaban sebelumnya, tetapi begini: (ini lebih pemula / tingkat menengah)

  • jelas: kering
  • jalankan loop ke belakang sehingga Anda selalu membandingkan ke 0 daripada variabel
  • gunakan operator bitwise kapan pun Anda bisa
  • pecah kode berulang menjadi modul / fungsi
  • objek cache
  • variabel lokal memiliki sedikit keunggulan kinerja
  • batasi manipulasi string sebanyak mungkin
Harun
sumber
4
Tentang pengulangan ke belakang: ya, perbandingan untuk pengulangan akan lebih cepat. Biasanya Anda menggunakan variabel untuk mengindeks ke dalam memori, dan mengaksesnya terbalik mungkin kontra produktif karena sering terjadi cache yang meleset (tidak ada pengambilan awal).
Andreas Reiff
1
AFAIK, dalam kebanyakan kasus, pengoptimal yang masuk akal akan baik-baik saja dengan loop, tanpa programmer harus secara eksplisit berjalan secara terbalik. Pengoptimal akan membalikkan loop itu sendiri, atau memiliki cara lain yang sama baiknya. Saya telah mencatat output ASM identik untuk (diakui relatif sederhana) loop ditulis baik naik vs max dan turun vs 0. Tentu saja, Z80 hari saya memiliki kebiasaan refleks menulis loop mundur, tapi saya curiga menyebut itu untuk pemula biasanya merupakan red herring / optimisasi prematur, ketika kode yang dapat dibaca & belajar praktik yang lebih penting harus menjadi prioritas.
underscore_d
Sebaliknya, menjalankan loop mundur akan lebih lambat dalam bahasa tingkat yang lebih rendah karena dalam perang antara perbandingan dengan nol ditambah pengurangan tambahan vs perbandingan integer tunggal, perbandingan integer tunggal lebih cepat. Alih-alih mengurangi, Anda dapat memiliki pointer ke alamat mulai di memori dan pointer ke alamat akhir di memori. Kemudian, tambahkan mulai pointer sampai sama dengan pointer akhir. Ini akan menghilangkan operasi penyeimbangan memori ekstra dalam kode rakitan, sehingga membuktikan performa yang jauh lebih baik.
Jack Giffin
5

Tidak mungkin dikatakan. Tergantung pada seperti apa kode itu. Jika kita dapat berasumsi bahwa kode itu sudah ada, maka kita bisa melihatnya dan mencari tahu, bagaimana cara mengoptimalkannya.

Lokalitas cache yang lebih baik, loop terbuka, Coba hilangkan rantai ketergantungan lama, untuk mendapatkan paralelisme tingkat instruksi yang lebih baik. Pilih gerakan kondisional di atas cabang jika memungkinkan. Manfaatkan petunjuk SIMD bila memungkinkan.

Pahami apa yang dilakukan kode Anda, dan pahami perangkat keras yang digunakan. Maka menjadi cukup sederhana untuk menentukan apa yang perlu Anda lakukan untuk meningkatkan kinerja kode Anda. Itu benar-benar nasihat yang benar-benar umum yang bisa saya pikirkan.

Nah, itu, dan "Tunjukkan kode pada SO dan minta saran optimasi untuk potongan kode tertentu".

jalf
sumber
5

Jika perangkat keras yang lebih baik adalah sebuah pilihan maka pasti pergi untuk itu. Jika tidak

  • Pastikan Anda menggunakan opsi kompilator dan tautan yang terbaik.
  • Jika hotspot rutin di perpustakaan yang berbeda ke pemanggil sering, pertimbangkan untuk memindahkan atau mengkloningnya ke modul pemanggil. Menghilangkan beberapa overhead panggilan dan dapat meningkatkan hit cache (lihat bagaimana AIX menautkan strcpy () secara statis ke objek bersama yang terhubung secara terpisah). Ini tentu saja dapat mengurangi hit cache juga, itulah sebabnya satu ukuran.
  • Lihat apakah ada kemungkinan menggunakan versi khusus dari rutin hotspot. Kekurangannya adalah lebih dari satu versi untuk dipertahankan.
  • Lihatlah assembler. Jika Anda pikir itu bisa lebih baik, pertimbangkan mengapa kompiler tidak mengetahui hal ini, dan bagaimana Anda dapat membantu kompilator.
  • Pertimbangkan: apakah Anda benar-benar menggunakan algoritma terbaik? Apakah ini algoritma terbaik untuk ukuran input Anda?
mealnor
sumber
Saya akan menambah par pertama Anda .: jangan lupa mematikan semua info debug di opsi kompiler Anda .
varnie
5

Cara google adalah salah satu opsi "Cache itu .. Sebisa mungkin jangan menyentuh disk"

asyncwait
sumber
5

Berikut adalah beberapa teknik optimasi cepat dan kotor yang saya gunakan. Saya menganggap ini sebagai optimasi 'first pass'.

Pelajari di mana waktu dihabiskan Cari tahu persis apa yang meluangkan waktu. Apakah ini file IO? Apakah ini waktu CPU? Apakah ini jaringan? Apakah ini Basis Data? Tidak ada gunanya mengoptimalkan untuk IO jika itu bukan hambatan.

Ketahui Lingkungan Anda Mengetahui tempat untuk mengoptimalkan biasanya tergantung pada lingkungan pengembangan. Dalam VB6, misalnya, melewati dengan referensi lebih lambat daripada melewati dengan nilai, tetapi dalam C dan C ++, dengan referensi jauh lebih cepat. Di C, masuk akal untuk mencoba sesuatu dan melakukan sesuatu yang berbeda jika kode pengembalian menunjukkan kegagalan, sementara di Dot Net, menangkap pengecualian jauh lebih lambat daripada memeriksa kondisi yang valid sebelum mencoba.

Indeks Membangun indeks pada bidang basis data yang sering ditanyakan. Anda hampir selalu dapat bertukar ruang untuk kecepatan.

Hindari pencarian Di dalam loop untuk dioptimalkan, saya menghindari harus melakukan pencarian. Temukan offset dan / atau indeks di luar loop dan gunakan kembali data di dalamnya.

Minimalkan IO coba desain dengan cara yang mengurangi berapa kali Anda harus membaca atau menulis terutama melalui koneksi jaringan

Kurangi Abstraksi Semakin banyak lapisan abstraksi yang harus dikerjakan kode, semakin lambat. Di dalam lingkaran kritis, kurangi abstraksi (mis. Ungkapkan metode tingkat rendah yang menghindari kode tambahan)

Spawn Threads untuk proyek dengan antarmuka pengguna, menelurkan utas baru untuk membentuk tugas yang lebih lambat membuat aplikasi terasa lebih responsif, meskipun tidak.

Pra-proses Anda biasanya dapat memperdagangkan ruang untuk kecepatan. Jika ada perhitungan atau operasi intensif lainnya, lihat apakah Anda dapat melakukan precompute beberapa informasi sebelum Anda berada di loop kritis.

Andrew Neely
sumber
5

Jika Anda memiliki banyak matematika floating point yang sangat paralel - terutama presisi tunggal - coba turunkan ke prosesor grafis (jika ada) menggunakan OpenCL atau (untuk chip NVidia) CUDA. GPU memiliki daya komputasi floating point yang sangat besar dalam shader mereka, yang jauh lebih besar daripada CPU.

Demi
sumber
5

Menambahkan jawaban ini karena saya tidak melihatnya termasuk dalam semua yang lain.

Minimalkan konversi implisit antara jenis dan tanda:

Ini berlaku untuk C / C ++ setidaknya, Bahkan jika Anda sudah berpikir Anda bebas dari konversi - kadang-kadang baik untuk menguji menambahkan peringatan kompiler di sekitar fungsi yang membutuhkan kinerja, terutama waspada terhadap konversi dalam loop.

Spesifik GCC: Anda dapat menguji ini dengan menambahkan beberapa pragma bertele-tele di sekitar kode Anda,

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

Saya telah melihat kasus di mana Anda bisa mendapatkan beberapa persen percepatan dengan mengurangi konversi yang ditimbulkan oleh peringatan seperti ini.

Dalam beberapa kasus, saya memiliki tajuk dengan peringatan ketat yang saya tetap sertakan untuk mencegah konversi tidak disengaja, namun ini merupakan trade-off karena Anda mungkin akhirnya menambahkan banyak gips ke konversi yang disengaja yang disengaja yang mungkin hanya membuat kode lebih berantakan untuk minimal keuntungan.

ideasman42
sumber
Inilah sebabnya saya suka itu di OCaml, casting antara tipe numerik harus xplicit.
Gayus
@ Titik adil Gayus - tetapi dalam banyak kasus mengubah bahasa bukanlah pilihan yang realistis. Karena C / C ++ digunakan secara luas, berguna untuk membuatnya lebih ketat, meskipun kompilernya spesifik.
ideasman42
4

Terkadang mengubah tata letak data Anda dapat membantu. Di C, Anda dapat beralih dari array atau struktur ke struktur array, atau sebaliknya.

Nosredna
sumber
4

Tweak OS dan kerangka kerjanya.

Mungkin terdengar berlebihan tetapi pikirkan seperti ini: Sistem Operasi dan Kerangka dirancang untuk melakukan banyak hal. Aplikasi Anda hanya melakukan hal-hal yang sangat spesifik. Jika Anda bisa mendapatkan OS lakukan dengan tepat apa yang dibutuhkan aplikasi Anda dan membuat aplikasi Anda memahami bagaimana kerangka kerja (php, .net, java) bekerja, Anda bisa mendapatkan jauh lebih baik dari perangkat keras Anda.

Facebook, misalnya, mengubah beberapa kernel levelys di Linux, mengubah cara kerja memcached (misalnya mereka menulis proxy memcached, dan menggunakan udp alih-alih tcp ).

Contoh lain untuk ini adalah Window2008. Win2K8 memiliki versi di mana Anda dapat menginstal hanya OS dasar yang diperlukan untuk menjalankan aplikasi X (misalnya Aplikasi Web, Aplikasi Server). Ini mengurangi banyak overhead yang dimiliki OS pada menjalankan proses dan memberi Anda kinerja yang lebih baik.

Tentu saja, Anda harus selalu memasukkan lebih banyak perangkat keras sebagai langkah pertama ...

Nir Levy
sumber
2
Itu akan menjadi pendekatan yang valid setelah semua pendekatan lain gagal, atau jika fitur OS atau Framework tertentu bertanggung jawab atas penurunan kinerja, tetapi tingkat keahlian dan kontrol yang diperlukan untuk melakukan itu mungkin tidak tersedia untuk setiap proyek.
Andrew Neely