Saya sedang berbicara tentang cara kami menulis rutinitas sederhana untuk meningkatkan kinerja tanpa membuat kode Anda lebih sulit dibaca ... misalnya, ini adalah tipikal yang kami pelajari:
for(int i = 0; i < collection.length(); i++ ){
// stuff here
}
Tapi, saya biasanya melakukan ini ketika a foreach
tidak berlaku:
for(int i = 0, j = collection.length(); i < j; i++ ){
// stuff here
}
Saya pikir ini adalah pendekatan yang lebih baik karena hanya akan memanggil length
metode sekali saja ... pacar saya bilang itu samar. Apakah ada trik sederhana lain yang Anda gunakan pada perkembangan Anda sendiri?
code-quality
performance
Cristian
sumber
sumber
Jawaban:
masukkan kuliah prematur-diskusi-adalah-akar-dari-semua-kejahatan
Yang mengatakan, berikut adalah beberapa kebiasaan saya sudah masuk untuk menghindari efisiensi yang tidak perlu, dan dalam beberapa kasus, membuat kode saya lebih sederhana dan lebih benar juga.
Ini bukan diskusi tentang prinsip-prinsip umum, tetapi beberapa hal yang harus diperhatikan untuk menghindari memasukkan inefisiensi yang tidak perlu ke dalam kode.
Ketahui big-O Anda
Ini mungkin harus digabungkan ke dalam diskusi panjang di atas. Cukup masuk akal bahwa loop di dalam loop, di mana loop dalam mengulangi perhitungan, akan menjadi lebih lambat. Sebagai contoh:
Ini akan membutuhkan banyak waktu jika string benar-benar panjang, karena panjangnya dihitung ulang pada setiap iterasi dari loop. Perhatikan bahwa GCC sebenarnya mengoptimalkan kasus ini karena
strlen()
ditandai sebagai fungsi murni.Saat menyortir sejuta bilangan bulat 32-bit, bubble sort akan menjadi cara yang salah . Secara umum, penyortiran dapat dilakukan dalam waktu O (n * log n) (atau lebih baik, dalam hal penyortiran radix), jadi kecuali Anda tahu data Anda akan menjadi kecil, cari algoritma yang setidaknya O (n * log n).
Demikian juga, ketika berhadapan dengan basis data, waspadai indeks. Jika Anda
SELECT * FROM people WHERE age = 20
, dan Anda tidak memiliki indeks pada orang (usia), itu akan memerlukan pemindaian berurutan O (n) daripada pemindaian indeks O (log n) yang jauh lebih cepat.Hirarki aritmatika integer
Ketika pemrograman dalam C, ingatlah bahwa beberapa operasi aritmatika lebih mahal daripada yang lain. Untuk bilangan bulat, hierarki berbunyi seperti ini (paling murah dulu):
+ - ~ & | ^
<< >>
*
/
Memang, compiler akan hal-hal yang biasanya mengoptimalkan seperti
n / 2
untukn >> 1
secara otomatis jika Anda menargetkan komputer mainstream, tetapi jika Anda menargetkan perangkat tertanam, Anda mungkin tidak mendapatkan kemewahan itu.Juga,
% 2
dan& 1
memiliki semantik yang berbeda. Pembagian dan modulus biasanya membulat ke nol, tetapi implementasinya didefinisikan. Baik>>
dan&
selalu berputar ke arah infinity negatif, yang (menurut saya) jauh lebih masuk akal. Misalnya, di komputer saya:Karena itu, gunakan apa yang masuk akal. Jangan berpikir Anda menjadi anak yang baik dengan menggunakan
% 2
ketika Anda awalnya akan menulis& 1
.Operasi floating point yang mahal
Hindari operasi floating point berat seperti
pow()
danlog()
dalam kode yang tidak benar-benar membutuhkannya, terutama ketika berhadapan dengan bilangan bulat. Ambil, misalnya, membaca nomor:Tidak hanya penggunaan ini
pow()
(dan konversiint
<-> yangdouble
diperlukan untuk menggunakannya) agak mahal, tetapi juga menciptakan peluang untuk kehilangan presisi (kebetulan, kode di atas tidak memiliki masalah presisi). Itu sebabnya saya meringis ketika saya melihat fungsi jenis ini digunakan dalam konteks non-matematika.Perhatikan juga bagaimana algoritma "pintar" di bawah ini, yang dikalikan 10 pada setiap iterasi, sebenarnya lebih ringkas daripada kode di atas:
sumber
strlen()
memeriksa string yang ditunjukkan oleh argumen penunjuknya, yang berarti itu tidak bisa berupa const. Juga,strlen()
memang ditandai sebagai murni dalam glibcstring.h
pure
atauconst
bahkan mendokumentasikannya di file header karena perbedaan halus antara keduanya. docs.parrot.org/parrot/1.3.0/html/docs/dev/c_functions.pod.htmlDari pertanyaan Anda dan utas komentar, sepertinya Anda "berpikir" bahwa perubahan kode ini meningkatkan kinerja, tetapi Anda tidak benar-benar tahu apakah itu benar atau tidak.
Saya penggemar filosofi Kent Beck :
Teknik saya untuk meningkatkan kinerja kode, pertama-tama dapatkan kode yang lulus tes unit dan diperhitungkan dengan baik dan kemudian (terutama untuk operasi perulangan) menulis tes unit yang memeriksa kinerja dan kemudian memperbaiki kode atau memikirkan algoritma yang berbeda jika yang saya ' Saya telah memilih tidak bekerja seperti yang diharapkan.
Misalnya, untuk menguji kecepatan dengan .NET code, saya menggunakan atribut Timeout NUnit untuk menulis pernyataan bahwa panggilan ke metode tertentu akan dijalankan dalam jumlah waktu tertentu.
Menggunakan sesuatu seperti atribut batas waktu NUnit dengan contoh kode yang Anda berikan (dan sejumlah besar iterasi untuk loop), Anda benar-benar dapat membuktikan apakah "peningkatan" Anda terhadap kode benar-benar membantu kinerja perfoma loop itu.
Satu penafian: Meskipun ini efektif pada level "mikro", ini tentu saja bukan satu-satunya cara untuk menguji kinerja dan tidak memperhitungkan masalah akun yang mungkin muncul pada level "makro" - tetapi ini adalah awal yang baik.
sumber
Ingatlah bahwa kompiler Anda dapat berubah:
ke:
atau yang serupa, jika
collection
tidak berubah selama loop.Jika kode ini ada di bagian kritis waktu aplikasi Anda, ada baiknya mencari tahu apakah ini masalahnya atau tidak - atau memang apakah Anda dapat mengubah opsi kompiler untuk melakukan ini.
Ini akan menjaga keterbacaan kode (karena yang pertama adalah apa yang diharapkan kebanyakan orang), sambil memberi Anda beberapa siklus mesin tambahan. Anda kemudian bebas berkonsentrasi pada area lain di mana kompiler tidak dapat membantu Anda.
Di samping catatan: jika Anda mengubah
collection
di dalam loop dengan menambahkan atau menghapus elemen (ya, saya tahu itu ide yang buruk, tetapi itu memang terjadi) maka contoh kedua Anda tidak akan mengulang semua elemen atau akan mencoba untuk mengakses masa lalu akhir array.sumber
Optimalisasi semacam ini biasanya tidak disarankan. Sepotong optimasi dapat dengan mudah dilakukan oleh kompiler, Anda bekerja dengan bahasa pemrograman tingkat yang lebih tinggi daripada perakitan, jadi pikirkan di tingkat yang sama.
sumber
Ini mungkin tidak berlaku terlalu banyak untuk tujuan umum pengkodean, tapi saya sebagian besar menanamkan pengembangan hari ini. Kami memiliki prosesor target spesifik (yang tidak akan menjadi lebih cepat - itu akan tampak usang pada saat mereka pensiun sistem dalam 20+ tahun), dan tenggat waktu waktu yang sangat ketat untuk sebagian besar kode. Prosesor, seperti semua prosesor, memiliki kebiasaan tertentu mengenai operasi mana yang cepat atau lambat.
Kami memiliki teknik yang digunakan untuk memastikan kami menghasilkan kode yang paling efisien, dengan tetap menjaga kewajaran bagi seluruh tim. Di tempat-tempat di mana konstruksi bahasa paling alami tidak menghasilkan kode yang paling efisien, kami telah membuat makro yang memastikan kode optimal digunakan. Jika kami melakukan proyek lanjutan untuk prosesor yang berbeda, kami dapat memperbarui makro untuk metode optimal pada prosesor itu.
Sebagai contoh spesifik, untuk prosesor kami saat ini, cabang mengosongkan pipa, menghentikan prosesor selama 8 siklus. Kompiler mengambil kode ini:
dan mengubahnya menjadi setara dengan perakitan
Ini akan membutuhkan 3 siklus, atau 10 jika dilompati
isReady=1;
. Tetapi prosesor memilikimax
instruksi satu siklus , jadi jauh lebih baik untuk menulis kode untuk menghasilkan urutan ini yang dijamin akan selalu mengambil 3 siklus:Jelas, maksud di sini kurang jelas dari aslinya. Jadi kami telah membuat makro, yang kami gunakan setiap kali kami ingin perbandingan boolean Greater-Than:
Kita dapat melakukan hal serupa untuk perbandingan lainnya. Untuk orang luar, kodenya sedikit lebih mudah dibaca daripada jika kita hanya menggunakan konstruk alami. Namun dengan cepat menjadi jelas setelah menghabiskan sedikit waktu bekerja dengan kode, dan itu jauh lebih baik daripada membiarkan setiap programmer bereksperimen dengan teknik optimasi mereka sendiri.
sumber
Nah, saran pertama adalah untuk menghindari optimisasi dini seperti itu sampai Anda tahu persis apa yang terjadi pada kode, sehingga Anda yakin bahwa Anda benar-benar membuatnya lebih cepat, dan tidak lebih lambat.
Dalam C # misalnya kompiler akan mengoptimalkan kode jika Anda mengulang panjang array, karena ia tahu bahwa ia tidak harus memeriksa indeks ketika Anda mengakses array. Jika Anda mencoba mengoptimalkannya dengan meletakkan panjang array dalam sebuah variabel, Anda akan memutuskan koneksi antara loop dan array, dan benar-benar membuat kode lebih lambat.
Jika Anda akan mengoptimalkan mikro, Anda harus membatasi diri pada hal-hal yang diketahui menggunakan banyak sumber daya. Jika hanya ada sedikit peningkatan kinerja, Anda harus menggunakan kode yang paling mudah dibaca dan dikelola. Bagaimana komputer bekerja berubah seiring waktu, sehingga sesuatu yang Anda temukan sedikit lebih cepat sekarang, mungkin tidak tetap seperti itu.
sumber
Saya punya teknik yang sangat sederhana.
Ada banyak waktu di mana menghemat waktu untuk menghindari proses ini, tetapi secara umum Anda akan tahu apakah itu masalahnya. Jika ada keraguan, saya tetap menggunakan ini secara default.
sumber
Manfaatkan hubungan arus pendek:
if(someVar || SomeMethod())
hanya membutuhkan waktu selama kode, dan dapat dibaca sebagai:
if(someMethod() || someVar)
namun itu akan mengevaluasi lebih cepat dari waktu ke waktu.
sumber
Tunggu enam bulan, suruh bos Anda untuk membeli semua komputer baru. Serius. Waktu pemrogram jauh lebih mahal daripada perangkat keras dalam jangka panjang. Komputer berkinerja tinggi memungkinkan pembuat kode untuk menulis kode secara langsung tanpa harus khawatir tentang kecepatan.
sumber
Cobalah untuk tidak mengoptimalkan terlalu banyak sebelumnya, maka ketika Anda mengoptimalkan, khawatirkan sedikit tentang keterbacaan.
Ada sedikit saya benci lebih dari kompleksitas yang tidak perlu, tetapi ketika Anda menekan situasi yang kompleks solusi yang kompleks sering diperlukan.
Jika Anda menulis kode dengan cara yang paling jelas maka buatlah komentar yang menjelaskan mengapa kode itu diubah ketika Anda membuat perubahan yang rumit.
Khusus untuk makna Anda, saya menemukan bahwa banyak kali melakukan kebalikan dari pendekatan default Boolean kadang-kadang membantu:
bisa menjadi
Dalam banyak bahasa, selama Anda membuat penyesuaian yang tepat untuk bagian "barang" dan itu masih dapat dibaca. Itu hanya tidak mendekati masalah dengan cara kebanyakan orang akan berpikir untuk melakukannya terlebih dahulu karena itu dihitung mundur.
dalam c # misalnya:
bisa juga ditulis sebagai:
(dan ya, Anda harus melakukannya dengan join atau stringbuilder, tapi saya mencoba membuat contoh sederhana)
Ada banyak trik lain yang dapat digunakan yang tidak sulit untuk diikuti tetapi banyak dari mereka tidak berlaku di semua bahasa seperti menggunakan pertengahan di sisi kiri penugasan dalam vb lama untuk menghindari hukuman penugasan kembali string atau membaca file teks dalam mode biner di .net untuk melewati penalti buffering ketika file terlalu besar untuk dibaca.
Satu-satunya kasus yang benar-benar generik yang dapat saya pikirkan yang akan berlaku di mana-mana adalah dengan menerapkan beberapa aljabar Boolean ke kondisi rumit untuk mencoba mengubah persamaan menjadi sesuatu yang memiliki peluang lebih baik untuk mengambil keuntungan dari kondisi hubungan arus pendek atau mengubah kompleks. mengatur pernyataan if-then atau case yang bersarang ke dalam persamaan sepenuhnya. Tak satu pun dari ini bekerja dalam semua kasus, tetapi mereka bisa menghemat waktu secara signifikan.
sumber
sumber
Gunakan alat terbaik yang dapat Anda temukan - kompiler yang baik, profiler yang baik, perpustakaan yang baik. Dapatkan algoritma yang benar, atau lebih baik lagi - gunakan perpustakaan yang tepat untuk melakukannya untuk Anda. Optimalisasi loop sepele adalah kentang kecil, ditambah Anda tidak sepintar kompilator pengoptimal.
sumber
Yang paling sederhana bagi saya adalah menggunakan tumpukan bila memungkinkan setiap kali pola penggunaan kasus umum cocok dengan kisaran, katakanlah, [0, 64) tetapi memiliki kasus langka yang tidak memiliki batas atas kecil.
Contoh C sederhana (sebelum):
Dan kemudian:
Saya telah menggeneralisasi seperti ini karena jenis hotspot ini banyak muncul dalam pembuatan profil:
Di atas menggunakan tumpukan ketika data yang dialokasikan cukup kecil dalam 99,9% kasus tersebut, dan menggunakan tumpukan sebaliknya.
Dalam C ++ saya telah menggeneralisasi ini dengan urutan kecil standar-compliant (mirip dengan
SmallVector
implementasi di luar sana) yang berputar di sekitar konsep yang sama.Ini bukan optimasi epik (saya sudah mendapatkan pengurangan dari, katakanlah, 3 detik untuk operasi untuk menyelesaikan hingga 1,8 detik), tetapi itu membutuhkan upaya sepele untuk diterapkan. Ketika Anda bisa mendapatkan sesuatu dari 3 detik menjadi 1,8 detik hanya dengan memperkenalkan sebaris kode dan mengubah dua, itu adalah pukulan yang cukup bagus untuk uang sekecil itu.
sumber
Yah ada banyak perubahan kinerja yang dapat Anda lakukan saat mengakses data yang akan berdampak besar pada aplikasi Anda. Jika Anda menulis kueri atau menggunakan ORM untuk mengakses databse, maka Anda perlu membaca beberapa buku tuning kinerja untuk backend database yang Anda gunakan. Kemungkinannya adalah Anda menggunakan teknologi yang dikenal berkinerja buruk. Tidak ada alasan untuk melakukan ini kecuali ketidaktahuan. Ini bukan optimasi prematur (saya mengutuk orang yang mengatakan ini karena telah begitu banyak saling terkait karena tidak pernah khawatir tentang kinerja), ini adalah desain yang bagus.
Hanya contoh singkat peningkatan kinerja untuk SQL Server: Gunakan indeks yang sesuai, hindari kursor - gunakan logika berbasis set, gunakan sargable di mana klausa, jangan pancang pandangan di atas tampilan, jangan kembalikan data lebih dari yang Anda butuhkan atau lebih kolom dari yang Anda butuhkan, jangan gunakan subqueries berkorelasi.
sumber
Jika ini adalah C ++, Anda harus terbiasa
++i
daripadai++
.++i
tidak akan pernah lebih buruk, itu berarti persis sama dengan pernyataan yang berdiri sendiri, dan dalam beberapa kasus itu bisa menjadi peningkatan kinerja.Tidak ada gunanya mengubah kode yang sudah ada jika itu bisa membantu, tetapi merupakan kebiasaan yang baik untuk masuk.
sumber
Saya punya pandangan yang sedikit berbeda. Hanya dengan mengikuti saran yang Anda dapatkan di sini tidak akan membuat banyak perbedaan, karena ada beberapa kesalahan yang perlu Anda lakukan, yang kemudian perlu Anda perbaiki, yang kemudian perlu Anda pelajari.
Kesalahan yang perlu Anda lakukan adalah mendesain struktur data Anda seperti yang dilakukan semua orang. Yaitu, dengan data yang berlebihan dan banyak lapisan abstraksi, dengan properti dan notifikasi yang merambat di seluruh struktur berusaha untuk tetap konsisten.
Maka kamu perlu melakukan penyetelan kinerja (profiling) dan membuatnya menunjukkan kepada Anda bagaimana, dalam banyak hal, berapa biaya yang harus Anda keluarkan untuk siklus adalah banyaknya lapisan abstraksi, dengan properti dan notifikasi yang merambat di seluruh struktur berusaha agar tetap konsisten.
Anda mungkin dapat memperbaiki masalah ini tanpa perubahan besar pada kode.
Maka jika Anda beruntung, Anda bisa belajar bahwa struktur data yang lebih sedikit lebih baik, dan lebih baik untuk bisa mentolerir ketidakkonsistenan sementara daripada mencoba untuk menjaga banyak hal tetap sesuai dengan gelombang pesan.
Bagaimana Anda menulis loop sama sekali tidak ada hubungannya dengan itu.
sumber