Saya adalah orang yang religius dan berusaha untuk tidak melakukan dosa. Itulah sebabnya saya cenderung menulis fungsi-fungsi kecil ( lebih kecil dari itu , untuk menulis ulang Robert C. Martin) untuk mematuhi beberapa perintah yang diperintahkan oleh Alkitab Kode Bersih . Tetapi ketika memeriksa beberapa hal, saya mendarat di pos ini , di bawah ini saya membaca komentar ini:
Ingat bahwa biaya pemanggilan metode bisa signifikan, tergantung pada bahasanya. Hampir selalu ada tradeoff antara menulis kode yang dapat dibaca dan menulis kode pemain.
Dalam kondisi apa pernyataan yang dikutip ini masih berlaku saat ini mengingat industri kaya dari kompiler modern yang berprestasi?
Itu satu-satunya pertanyaan saya. Dan ini bukan tentang apakah saya harus menulis fungsi yang panjang atau kecil. Saya hanya menyoroti bahwa umpan balik Anda mungkin - atau tidak - berkontribusi untuk mengubah sikap saya dan membuat saya tidak dapat menahan godaan penghujat .
sumber
for(Integer index = 0, size = someList.size(); index < size; index++)
alih-alih secara sederhanafor(Integer index = 0; index < someList.size(); index++)
. Hanya karena kompiler Anda dibuat dalam beberapa tahun terakhir tidak berarti Anda dapat melepaskan profil.main()
, yang lain membagi semuanya menjadi sekitar 50 fungsi kecil, dan semuanya sama sekali tidak dapat dibaca. Kuncinya adalah, seperti biasa, untuk mencapai keseimbangan yang baik .Jawaban:
Itu tergantung pada domain Anda.
Jika Anda menulis kode untuk mikrokontroler berdaya rendah, maka biaya panggilan metode mungkin signifikan. Tetapi jika Anda membuat situs web atau aplikasi normal, maka biaya panggilan metode akan diabaikan dibandingkan dengan sisa kode. Dalam hal ini, akan selalu lebih baik berfokus pada algoritma dan struktur data yang tepat daripada optimasi mikro seperti pemanggilan metode.
Dan ada juga pertanyaan tentang kompiler yang menjelaskan metode untuk Anda. Sebagian besar kompiler cukup cerdas untuk menjalankan fungsi sebisa mungkin.
Dan terakhir, ada aturan kinerja emas: SELALU PROFIL PERTAMA. Jangan menulis kode "dioptimalkan" berdasarkan asumsi. Jika Anda tidak terbiasa, tulis kedua kasing dan lihat mana yang lebih baik.
sumber
Fungsi panggilan overhead tergantung sepenuhnya pada bahasa, dan pada tingkat apa Anda mengoptimalkan.
Pada tingkat yang sangat rendah, pemanggilan fungsi dan bahkan pemanggilan metode virtual mungkin lebih mahal jika menyebabkan misprediksi cabang atau kesalahan cache CPU. Jika Anda telah menulis assembler , Anda juga akan tahu bahwa Anda memerlukan beberapa instruksi tambahan untuk menyimpan dan mengembalikan register di sekitar panggilan. Tidak benar bahwa kompiler "cukup pintar" akan dapat menyejajarkan fungsi yang benar untuk menghindari overhead ini, karena kompiler dibatasi oleh semantik bahasa (terutama di sekitar fitur seperti pengiriman metode antarmuka atau perpustakaan yang dimuat secara dinamis).
Pada level yang tinggi, bahasa seperti Perl, Python, Ruby melakukan banyak pembukuan per panggilan fungsi, membuatnya menjadi mahal. Ini diperburuk oleh meta-pemrograman. Saya pernah mempercepat software Python 3x hanya dengan mengangkat fungsi memanggil dari loop yang sangat panas. Dalam kode kritis-kinerja, fungsi pembantu inlining dapat memiliki efek yang nyata.
Tetapi sebagian besar perangkat lunak tidak terlalu kritis terhadap kinerja sehingga Anda dapat melihat fungsi panggilan overhead. Bagaimanapun, menulis bersih, kode sederhana terbayar:
Jika kode Anda tidak kritis terhadap kinerja, ini membuat pemeliharaan lebih mudah. Bahkan dalam perangkat lunak yang sangat kritis terhadap kinerja, sebagian besar kode tidak akan menjadi “hot spot”.
Jika kode Anda kritis terhadap kinerja, kode sederhana membuatnya lebih mudah untuk memahami kode dan melihat peluang untuk optimisasi. Kemenangan terbesar biasanya tidak datang dari optimasi mikro seperti fungsi inlining, tetapi dari peningkatan algoritmik. Atau diutarakan secara berbeda: jangan melakukan hal yang sama lebih cepat. Temukan cara untuk melakukan lebih sedikit.
Perhatikan bahwa "kode sederhana" tidak berarti "diperhitungkan dalam ribuan fungsi kecil". Setiap fungsi juga memperkenalkan sedikit overhead kognitif - lebih sulit untuk berpikir tentang kode yang lebih abstrak. Pada titik tertentu, fungsi-fungsi kecil ini mungkin melakukan sedikit sehingga tidak menggunakannya akan menyederhanakan kode Anda.
sumber
Hampir semua pepatah tentang kode tuning untuk kinerja adalah kasus khusus hukum Amdahl . Pernyataan pendek, lucu dari hukum Amdahl adalah
(Mengoptimalkan semuanya hingga nol persen dari runtime benar-benar mungkin: ketika Anda duduk untuk mengoptimalkan program yang besar dan rumit, Anda kemungkinan besar akan menemukan bahwa ia menghabiskan setidaknya beberapa runtime untuk hal- hal yang tidak perlu dilakukan sama sekali. .)
Inilah sebabnya mengapa orang biasanya mengatakan tidak perlu khawatir tentang biaya panggilan fungsi: tidak peduli seberapa mahal mereka, biasanya program secara keseluruhan hanya menghabiskan sebagian kecil dari runtime ongkos panggilan, jadi mempercepat mereka tidak banyak membantu .
Tapi, jika ada trik yang bisa Anda tarik yang membuat semua fungsi memanggil lebih cepat, trik itu mungkin sepadan. Pengembang kompiler menghabiskan banyak waktu untuk mengoptimalkan fungsi "prolog" dan "epilog", karena itu menguntungkan semua program yang dikompilasi dengan kompiler itu, meskipun hanya sedikit untuk masing-masingnya.
Dan, jika Anda memiliki alasan untuk percaya bahwa program ini menghabiskan banyak runtime yang hanya membuat panggilan fungsi, maka Anda harus mulai berpikir tentang apakah beberapa dari mereka fungsi panggilan yang tidak perlu. Berikut adalah beberapa aturan praktis untuk mengetahui kapan Anda harus melakukan ini:
Jika runtime per-pemanggilan suatu fungsi kurang dari satu milidetik, tetapi fungsi itu disebut ratusan ribu kali, mungkin harus digarisbawahi.
Jika profil program menunjukkan ribuan fungsi, dan tidak satu pun dari mereka mengambil lebih dari 0,1% atau lebih runtime, maka fungsi-panggilan overhead mungkin signifikan secara agregat.
Jika Anda memiliki " kode lasagna ," di mana ada banyak lapisan abstraksi yang hampir tidak bekerja di luar pengiriman ke lapisan berikutnya, dan semua lapisan ini diimplementasikan dengan panggilan metode virtual, maka ada kemungkinan besar CPU membuang-buang banyak waktu di warung pipa cabang tidak langsung. Sayangnya, satu-satunya obat untuk ini adalah menyingkirkan beberapa lapisan, yang seringkali sangat sulit.
sumber
final
kelas dan metode yang berlaku di Jawa, atau non-virtual
metode dalam C # atau C ++) maka tipuan dapat dihilangkan oleh kompiler / runtime dan Anda ' Saya akan melihat keuntungan tanpa restrukturisasi besar-besaran. Seperti yang ditunjukkan oleh @JorgWMittag di atas, JVM bahkan dapat sejajar dalam kasus di mana optimasi tidak dapat dilakukan ...Saya akan menantang kutipan ini:
Ini adalah pernyataan yang sangat menyesatkan, dan sikap yang berpotensi berbahaya. Ada beberapa kasus khusus di mana Anda harus melakukan tradeoff, tetapi secara umum kedua faktor tersebut bersifat independen.
Contoh pengorbanan yang diperlukan adalah ketika Anda memiliki algoritma sederhana versus yang lebih kompleks tetapi lebih banyak performan. Implementasi hashtable jelas lebih kompleks daripada implementasi daftar tertaut, tetapi pencarian akan lebih lambat, jadi Anda mungkin harus berdagang kesederhanaan (yang merupakan faktor dalam keterbacaan) untuk kinerja.
Mengenai overhead panggilan fungsi, mengubah algoritma rekursif menjadi iteratif mungkin memiliki manfaat yang signifikan tergantung pada algoritma dan bahasa. Tapi ini lagi skenario yang sangat spesifik, dan secara umum overhead panggilan fungsi akan diabaikan atau dioptimalkan.
(Beberapa bahasa dinamis seperti Python memang memiliki overhead panggilan metode yang signifikan. Tetapi jika kinerja menjadi masalah, Anda mungkin seharusnya tidak menggunakan Python sejak awal.)
Sebagian besar prinsip untuk kode yang dapat dibaca - pemformatan yang konsisten, nama pengenal yang bermakna, komentar yang sesuai dan bermanfaat dan sebagainya tidak berpengaruh pada kinerja. Dan beberapa - seperti menggunakan enum daripada string - juga memiliki manfaat kinerja.
sumber
Fungsi panggilan overhead tidak penting dalam banyak kasus.
Namun, keuntungan yang lebih besar dari kode inlining adalah mengoptimalkan kode baru setelah inlining .
Misalnya, jika Anda memanggil fungsi dengan argumen konstan, pengoptimal sekarang dapat melipat konstan argumen yang sebelumnya tidak dapat digarisbawahi panggilan. Jika argumennya adalah pointer fungsi (atau lambda), pengoptimal sekarang dapat menyejajarkan panggilan ke lambda itu juga.
Ini adalah alasan besar mengapa fungsi virtual dan pointer fungsi tidak menarik karena Anda tidak dapat menyejajarkannya sama sekali kecuali jika pointer fungsi aktual telah dilipat terus-menerus ke situs panggilan.
sumber
Dengan asumsi kinerja memang penting untuk program Anda, dan memang memiliki banyak panggilan, biayanya masih mungkin atau tidak masalah tergantung pada jenis panggilannya.
Jika fungsi yang dipanggil kecil, dan kompiler dapat menyatukannya, maka biayanya akan menjadi nol. Kompiler modern / implementasi bahasa memiliki JIT, tautan-waktu-optimasi dan / atau sistem modul yang dirancang untuk memaksimalkan kemampuan fungsi inline ketika itu bermanfaat.
OTOH, ada biaya yang tidak jelas untuk panggilan fungsi: keberadaan mereka hanya dapat menghambat optimasi kompiler sebelum dan sesudah panggilan.
Jika kompiler tidak dapat memberi alasan tentang apa fungsi yang dipanggil berfungsi (mis. Pengiriman virtual / dinamis atau fungsi dalam perpustakaan dinamis) maka mungkin harus berasumsi dengan pesimis bahwa fungsi tersebut dapat memiliki efek samping — melempar pengecualian, memodifikasi negara global, atau ubah memori yang terlihat melalui pointer. Kompiler mungkin harus menyimpan nilai sementara untuk mendukung memori dan membacanya kembali setelah panggilan. Itu tidak akan dapat memesan ulang instruksi di sekitar panggilan, sehingga mungkin tidak dapat melakukan vektorisasi loop atau hoist perhitungan yang berlebihan keluar dari loop.
Misalnya, jika Anda perlu memanggil fungsi di setiap iterasi loop:
Compiler mungkin tahu itu adalah fungsi murni, dan memindahkannya keluar dari loop (dalam kasus yang mengerikan seperti contoh ini bahkan memperbaiki algoritma O (n ^ 2) yang tidak disengaja menjadi O (n)):
Dan bahkan mungkin menulis ulang loop untuk memproses elemen 4/8/16 sekaligus menggunakan instruksi lebar / SIMD.
Tetapi jika Anda menambahkan panggilan ke beberapa kode buram dalam loop, bahkan jika panggilan tidak melakukan apa-apa dan sangat murah itu sendiri, kompiler harus menganggap yang terburuk - bahwa panggilan akan mengakses variabel global yang menunjuk ke memori yang sama dengan
s
perubahan isinya (bahkan jika ituconst
dalam fungsi Anda, itu bisa menjadi non-const
tempat lain), membuat optimasi tidak mungkin:sumber
Makalah lama ini mungkin menjawab pertanyaan Anda:
Abstrak:
sumber
Dalam C ++ waspadalah dalam merancang panggilan fungsi yang menyalin argumen, defaultnya adalah "lewat nilai". Fungsi panggilan overhead karena menyimpan register dan hal-hal lain yang berkaitan dengan stack-frame dapat dikuasai oleh salinan objek yang tidak disengaja (dan berpotensi sangat mahal).
Ada optimasi terkait tumpukan-bingkai yang harus Anda selidiki sebelum menyerah pada kode yang sangat diperhitungkan.
Sebagian besar waktu ketika saya harus berurusan dengan program lambat saya menemukan membuat perubahan algoritmik menghasilkan peningkatan kecepatan yang jauh lebih besar daripada panggilan fungsi in-lining. Sebagai contoh: insinyur lain membuat parser yang mengisi struktur peta-peta. Sebagai bagian dari itu, ia menghapus indeks cache dari satu peta ke yang terkait secara logis. Itu adalah langkah ketahanan kode yang bagus, namun itu membuat program tidak dapat digunakan karena faktor perlambatan 100 karena melakukan pencarian hash untuk semua akses masa depan dibandingkan dengan menggunakan indeks yang disimpan. Profiling menunjukkan bahwa sebagian besar waktu dihabiskan dalam fungsi hashing.
sumber
Ya, prediksi cabang yang terlewatkan lebih mahal pada perangkat keras modern daripada beberapa dekade yang lalu, tetapi kompiler telah menjadi jauh lebih pintar dalam mengoptimalkan ini.
Sebagai contoh, pertimbangkan Java. Sekilas, fungsi panggilan overhead harus sangat dominan dalam bahasa ini:
Ngeri dengan praktik-praktik ini, rata-rata programmer C akan memprediksi bahwa Java harus setidaknya satu urutan besarnya lebih lambat dari C. Dan 20 tahun yang lalu ia akan benar. Namun tolok ukur modern menempatkan kode Java idiomatik dalam beberapa persen dari kode C yang setara. Bagaimana mungkin?
Salah satu alasannya adalah bahwa fungsi inline JVM modern memanggil sebagai hal yang biasa. Itu melakukannya menggunakan spekulasi inlining:
Yaitu, kodenya:
akan ditulis ulang menjadi
Dan tentu saja runtime cukup pintar untuk naik ke pemeriksaan jenis ini selama titik tidak ditetapkan, atau buang jika jenisnya diketahui kode panggilan.
Singkatnya, jika bahkan Java mengelola metode otomatis inlining, tidak ada alasan yang melekat mengapa kompiler tidak dapat mendukung inlining otomatis, dan setiap alasan untuk melakukannya, karena inlining sangat bermanfaat pada prosesor modern. Karena itu saya hampir tidak bisa membayangkan kompiler arus utama modern yang tidak mengetahui strategi optimasi yang paling mendasar ini, dan akan menganggap kompiler yang mampu melakukan ini kecuali terbukti sebaliknya.
sumber
Seperti yang orang lain katakan, Anda harus mengukur kinerja program Anda terlebih dahulu, dan mungkin tidak akan menemukan perbedaan dalam praktiknya.
Namun, dari level konseptual, saya pikir saya akan menjelaskan beberapa hal yang tergabung dalam pertanyaan Anda. Pertama, Anda bertanya:
Perhatikan kata-kata kunci "fungsi" dan "penyusun". Kutipan Anda berbeda secara subtil:
Ini berbicara tentang metode , dalam arti berorientasi objek.
Sementara "fungsi" dan "metode" sering digunakan secara bergantian, ada perbedaan dalam hal biayanya (yang Anda tanyakan) dan ketika menyangkut kompilasi (yang merupakan konteks yang Anda berikan).
Secara khusus, kita perlu tahu tentang pengiriman statis vs pengiriman dinamis . Saya akan mengabaikan optimisasi untuk saat ini.
Dalam bahasa seperti C, kami biasanya memanggil fungsi dengan pengiriman statis . Sebagai contoh:
Ketika kompiler melihat panggilan
foo(y)
, ia tahu fungsi apa yangfoo
merujuk nama itu, sehingga program keluaran bisa langsung melompat kefoo
fungsi, yang cukup murah. Itulah arti pengiriman statis .Alternatifnya adalah pengiriman dinamis , di mana kompiler tidak tahu fungsi mana yang dipanggil. Sebagai contoh, inilah beberapa kode Haskell (karena setara C akan berantakan!):
Di sini
bar
fungsinya memanggil argumennyaf
, yang bisa berupa apa saja. Karenanya kompiler tidak bisa hanya mengkompilasibar
ke instruksi lompatan cepat, karena ia tidak tahu ke mana harus melompat. Sebagai gantinya, kode yang kita hasilkan untukbar
dereference akanf
mencari tahu fungsi yang ditunjuknya, lalu beralih ke sana. Itulah arti pengiriman dinamis .Kedua contoh tersebut adalah untuk fungsi . Anda menyebutkan metode , yang dapat dianggap sebagai gaya fungsi khusus yang dikirim secara dinamis. Sebagai contoh, inilah beberapa Python:
The
y.foo()
panggilan menggunakan dispatch dinamis, karena itu mencari nilai darifoo
properti diy
objek, dan memanggil apa pun yang ditemukan; tidak tahu bahway
akan ada kelasA
, atau bahwaA
kelas berisifoo
metode, jadi kita tidak bisa langsung langsung ke sana.OK, itu ide dasarnya. Perhatikan bahwa pengiriman statis lebih cepat daripada pengiriman dinamis terlepas dari apakah kami mengkompilasi atau menafsirkan; semuanya sama. Dereferencing dikenakan biaya tambahan.
Jadi bagaimana hal ini memengaruhi kompiler modern dan optimal?
Hal pertama yang perlu diperhatikan adalah pengiriman statis dapat dioptimalkan lebih berat: ketika kita tahu ke mana fungsi kita melompat, dapat melakukan hal-hal seperti inlining. Dengan pengiriman dinamis, kami tidak tahu bahwa kami akan melompat sampai waktu berjalan, jadi tidak banyak optimasi yang dapat kami lakukan.
Kedua, dimungkinkan dalam beberapa bahasa untuk menyimpulkan di mana beberapa pengiriman dinamis akan berakhir melompat, dan karenanya mengoptimalkannya menjadi pengiriman statis. Ini memungkinkan kami melakukan optimisasi lain seperti inlining, dll.
Dalam contoh Python di atas, kesimpulan semacam itu sangat tidak ada harapan, karena Python memungkinkan kode lain untuk mengesampingkan kelas dan properti, sehingga sulit untuk menyimpulkan banyak hal yang akan berlaku dalam semua kasus.
Jika bahasa kita memungkinkan kita memaksakan lebih banyak pembatasan, misalnya dengan membatasi
y
kelasA
menggunakan anotasi, maka kita dapat menggunakan informasi itu untuk menyimpulkan fungsi target. Dalam bahasa dengan subclassing (yang hampir semua bahasa dengan kelas!) Itu sebenarnya tidak cukup, karenay
mungkin sebenarnya memiliki kelas (sub) yang berbeda, jadi kita akan membutuhkan informasi tambahan sepertifinal
anotasi Java untuk mengetahui dengan tepat fungsi mana yang akan dipanggil.Haskell bukan bahasa OO, tapi kami dapat menyimpulkan nilai
f
oleh inliningbar
(yang statis dikirim) kemain
, menggantikanfoo
untuky
. Karena targetfoo
inmain
diketahui secara statis, panggilan menjadi dikirim secara statis, dan mungkin akan diuraikan dan dioptimalkan sepenuhnya (karena fungsi-fungsi ini kecil, kompiler lebih cenderung untuk menyejajarkannya, meskipun kita tidak dapat mengandalkannya secara umum ).Karenanya biaya turun ke:
Jika Anda menggunakan bahasa "sangat dinamis", dengan banyak pengiriman dinamis dan sedikit jaminan yang tersedia untuk kompiler, maka setiap panggilan akan dikenai biaya. Jika Anda menggunakan bahasa "sangat statis", maka kompiler yang matang akan menghasilkan kode yang sangat cepat. Jika Anda berada di antara keduanya, maka itu dapat bergantung pada gaya pengkodean Anda dan seberapa pintar implementasinya.
sumber
Sayangnya, ini sangat tergantung pada:
Pertama-tama, hukum pertama dari optimasi kinerja adalah profil terlebih dahulu . Ada banyak domain di mana kinerja bagian perangkat lunak tidak relevan dengan kinerja seluruh tumpukan: panggilan basis data, operasi jaringan, operasi OS, ...
Ini berarti bahwa kinerja perangkat lunak sama sekali tidak relevan, bahkan jika itu tidak meningkatkan latensi, mengoptimalkan perangkat lunak dapat menghasilkan penghematan energi dan penghematan perangkat keras (atau penghematan baterai untuk aplikasi seluler), yang dapat menjadi masalah.
Namun, itu biasanya TIDAK bisa eye-balled, dan sering kali peningkatan algoritmik mengalahkan optimasi mikro dengan margin besar.
Jadi, sebelum mengoptimalkan, Anda perlu memahami untuk apa Anda mengoptimalkan ... dan apakah itu layak.
Sekarang, berkenaan dengan kinerja perangkat lunak murni, ini sangat bervariasi antara toolchains.
Ada dua biaya untuk panggilan fungsi:
Biaya run time agak jelas; untuk melakukan panggilan fungsi diperlukan sejumlah pekerjaan. Sebagai contoh, menggunakan C pada x86, panggilan fungsi akan membutuhkan (1) menumpahkan register ke stack, (2) mendorong argumen ke register, melakukan panggilan, dan kemudian (3) mengembalikan register dari stack. Lihat ringkasan konvensi pemanggilan ini untuk melihat pekerjaan yang terlibat .
Tumpahan / pemulihan register ini membutuhkan waktu yang tidak sepele (puluhan siklus CPU).
Secara umum diharapkan bahwa biaya ini akan sepele dibandingkan dengan biaya aktual menjalankan fungsi, namun beberapa pola kontraproduktif di sini: getter, fungsi dijaga oleh kondisi sederhana, dll ...
Selain juru bahasa , seorang programmer akan berharap bahwa kompiler atau JIT mereka akan mengoptimalkan pemanggilan fungsi yang tidak perlu; meskipun harapan ini terkadang tidak membuahkan hasil. Karena pengoptimal bukanlah sihir.
Pengoptimal dapat mendeteksi bahwa panggilan fungsi adalah sepele, dan sebaris panggilan: pada dasarnya, salin / tempelkan badan fungsi di situs panggilan. Ini tidak selalu merupakan optimasi yang baik (dapat menyebabkan mengasapi) tetapi secara umum bermanfaat karena inlining memaparkan konteks , dan konteksnya memungkinkan lebih banyak optimisasi.
Contoh khas adalah:
Jika
func
inline, maka optimizer akan menyadari bahwa cabang tidak pernah diambil, dan mengoptimalkancall
untukvoid call() {}
.Dalam hal ini, pemanggilan fungsi, dengan menyembunyikan informasi dari pengoptimal (jika belum inline), dapat menghambat pengoptimalan tertentu. Panggilan fungsi virtual terutama bersalah karena ini, karena devirtualization (membuktikan fungsi mana yang akhirnya dipanggil pada saat run time) tidak selalu mudah.
Sebagai kesimpulan, saran saya adalah menulis dengan jelas terlebih dahulu, menghindari pesimisasi algoritme prematur (kompleksitas kubik atau gigitan yang lebih buruk dengan cepat), dan kemudian hanya mengoptimalkan apa yang perlu dioptimalkan.
sumber
Aku hanya akan mengatakan tidak pernah. Saya percaya kutipan itu sembrono untuk dibuang begitu saja.
Tentu saja saya tidak berbicara kebenaran yang lengkap, tetapi saya tidak terlalu peduli tentang kebenaran sebanyak itu. Ini seperti dalam film Matrix, saya lupa apakah itu 1 atau 2 atau 3 - saya pikir itu adalah satu dengan aktris Italia seksi dengan melon besar (saya tidak benar-benar suka kecuali yang pertama), ketika Wanita oracle mengatakan kepada Keanu Reeves, "Saya baru saja mengatakan kepada Anda apa yang perlu Anda dengar," atau sesuatu untuk efek ini, itulah yang ingin saya lakukan sekarang.
Pemrogram tidak perlu mendengar ini. Jika mereka berpengalaman dengan profiler di tangan mereka dan kutipan itu agak berlaku untuk kompiler mereka, mereka akan sudah tahu ini dan akan belajar ini dengan cara yang tepat asalkan mereka memahami output profil mereka dan mengapa panggilan daun tertentu adalah hotspot, melalui pengukuran. Jika mereka tidak berpengalaman dan tidak pernah memrofilkan kode mereka, ini adalah hal terakhir yang perlu mereka dengar, bahwa mereka harus mulai dengan takhayul mengkompromikan bagaimana mereka menulis kode sampai pada titik menggarisbawahi segala sesuatu bahkan sebelum mengidentifikasi hotspot dengan harapan bahwa itu akan menjadi lebih performant.
Bagaimanapun, untuk respons yang lebih akurat, itu tergantung. Beberapa persyaratan kapal sudah terdaftar di antara jawaban yang bagus. Kondisi yang mungkin hanya memilih satu bahasa sudah sangat besar, seperti C ++ yang harus masuk ke pengiriman dinamis dalam panggilan virtual dan ketika itu dapat dioptimalkan jauh dan di bawah mana kompiler dan bahkan linker, dan yang sudah menjamin tanggapan rinci apalagi mencoba untuk mengatasi kondisi dalam setiap bahasa yang mungkin dan kompiler di luar sana. Tetapi saya akan menambahkan di atas, "siapa yang peduli?" karena bahkan bekerja di area yang sangat kritis terhadap kinerja seperti raytracing, hal terakhir yang akan saya mulai lakukan di muka adalah metode hand-inlining sebelum saya melakukan pengukuran.
Saya percaya beberapa orang terlalu bersemangat untuk menyarankan Anda tidak boleh melakukan optimasi mikro sebelum mengukur. Jika mengoptimalkan lokalitas jumlah referensi sebagai optimasi mikro, maka saya sering mulai menerapkan optimasi seperti itu di awal dengan pola pikir desain berorientasi data di bidang yang saya tahu pasti akan sangat penting untuk kinerja (kode raytracing, misalnya), karena kalau tidak saya tahu saya harus menulis ulang bagian besar segera setelah bekerja di domain ini selama bertahun-tahun. Mengoptimalkan representasi data untuk hit cache seringkali dapat memiliki jenis peningkatan kinerja yang sama dengan peningkatan algoritmik kecuali jika kita berbicara seperti waktu kuadratik untuk linier.
Tetapi saya tidak pernah melihat alasan yang baik untuk memulai inlining sebelum pengukuran, terutama karena profiler layak mengungkapkan apa yang mungkin mendapat manfaat dari inlining, tetapi tidak mengungkapkan apa yang mungkin mendapat manfaat dari tidak di-inline (dan tidak inlining sebenarnya dapat membuat kode lebih cepat jika panggilan fungsi tak bergaris adalah kasus yang jarang terjadi, meningkatkan lokalitas referensi untuk icache untuk kode panas dan kadang-kadang bahkan memungkinkan pengoptimal untuk melakukan pekerjaan yang lebih baik untuk jalur kasus umum eksekusi).
sumber