Mengekstrak fungsionalitas menjadi metode atau fungsi adalah suatu keharusan untuk kode modularitas, keterbacaan dan interoperabilitas, terutama di OOP.
Tetapi ini berarti lebih banyak fungsi panggilan akan dilakukan.
Bagaimana memecah kode kami menjadi metode atau fungsi sebenarnya memengaruhi kinerja dalam bahasa * modern ?
* Yang paling populer: C, Java, C ++, C #, Python, JavaScript, Ruby ...
Jawaban:
Mungkin. Compiler mungkin memutuskan "hei, fungsi ini hanya dipanggil beberapa kali, dan saya seharusnya mengoptimalkan untuk kecepatan, jadi saya hanya akan sebaris fungsi ini". Pada dasarnya, kompiler akan menggantikan pemanggilan fungsi dengan tubuh fungsi. Sebagai contoh, kode sumber akan terlihat seperti ini.
Kompilator memutuskan untuk sebaris
DoSomethingElse
, dan kode menjadiKetika fungsi tidak diuraikan, ya ada hit kinerja untuk melakukan panggilan fungsi. Namun, ini adalah hit yang sangat kecil sehingga hanya kode kinerja sangat tinggi yang akan mengkhawatirkan panggilan fungsi. Dan pada proyek-proyek semacam itu, kodenya biasanya ditulis dalam pertemuan.
Panggilan fungsi (tergantung pada platform) biasanya melibatkan beberapa instruksi, dan itu termasuk menyimpan / mengembalikan tumpukan. Beberapa panggilan fungsi terdiri dari instruksi melompat dan kembali.
Tetapi ada hal-hal lain yang mungkin memengaruhi kinerja fungsi panggilan. Fungsi yang dipanggil mungkin tidak dimuat ke cache prosesor, menyebabkan cache gagal dan memaksa pengontrol memori untuk mengambil fungsi dari RAM utama. Ini dapat menyebabkan hit besar untuk kinerja.
Singkatnya: panggilan fungsi mungkin atau mungkin tidak mempengaruhi kinerja. Satu-satunya cara untuk mengetahui adalah membuat profil kode Anda. Jangan mencoba menebak di mana letak kode lambat, karena kompiler dan perangkat keras memiliki beberapa trik luar biasa. Profil kode untuk mendapatkan lokasi titik lambat.
sumber
Ini adalah masalah implementasi dari kompiler atau runtime (dan opsinya) dan tidak dapat dikatakan dengan pasti.
Di dalam C dan C ++, beberapa kompiler akan inline panggilan berdasarkan pada pengaturan optimisasi - ini dapat dilihat secara sepele dengan memeriksa rakitan yang dihasilkan ketika melihat alat seperti https://gcc.godbolt.org/
Bahasa lain, seperti Java, memiliki ini sebagai bagian dari runtime. Ini adalah bagian dari JIT dan diuraikan dalam pertanyaan SO ini . Dalam tampilan paticular pada opsi JVM untuk HotSpot
Jadi ya, kompiler JIT HotSpot akan inline metode yang memenuhi kriteria tertentu.
The dampak dari ini, sulit untuk menentukan karena setiap JVM (atau compiler) dapat melakukan sesuatu yang berbeda dan mencoba untuk menjawab dengan stroke luas bahasa hampir pasti salah. Dampaknya hanya dapat ditentukan dengan benar dengan membuat profil kode di lingkungan berjalan yang sesuai dan memeriksa output yang dikompilasi.
Ini dapat dilihat sebagai pendekatan yang salah arah dengan CPython tidak inlining, tetapi Jython (Python berjalan di JVM) memiliki beberapa panggilan yang digarisbawahi. Demikian juga MRI Ruby tidak inlining sementara JRuby mau, dan ruby2c yang merupakan transpiler untuk ruby ke C ... yang kemudian bisa inlining atau tidak tergantung pada opsi kompiler C yang dikompilasi.
Bahasa tidak sebaris. Implementasi dapat .
sumber
Anda mencari kinerja di tempat yang salah. Masalah dengan panggilan fungsi bukan karena harganya yang mahal. Ada masalah lain. Panggilan fungsi bisa benar-benar gratis, dan Anda masih memiliki masalah lain ini.
Itu adalah fungsi seperti kartu kredit. Karena Anda dapat dengan mudah menggunakannya, Anda cenderung menggunakannya lebih dari yang seharusnya. Misalkan Anda menyebutnya 20% lebih dari yang Anda butuhkan. Kemudian, perangkat lunak besar yang khas berisi beberapa lapisan, masing-masing fungsi panggilan di lapisan di bawah, sehingga faktor 1.2 dapat diperparah dengan jumlah lapisan. (Misalnya, jika ada lima lapisan, dan setiap lapisan memiliki faktor perlambatan 1,2, faktor perlambatan majemuk adalah 1,2 ^ 5 atau 2,5.) Ini hanya satu cara untuk memikirkannya.
Ini tidak berarti Anda harus menghindari panggilan fungsi. Maksudnya adalah, ketika kode aktif dan berjalan, Anda harus tahu cara menemukan dan menghilangkan pemborosan. Ada banyak saran bagus tentang cara melakukan ini di situs stackexchange. Ini memberikan salah satu kontribusi saya.
TAMBAH: Contoh kecil. Suatu kali saya bekerja dalam tim perangkat lunak di pabrik yang melacak serangkaian perintah kerja atau "pekerjaan". Ada fungsi
JobDone(idJob)
yang bisa mengetahui apakah suatu pekerjaan dilakukan. Suatu pekerjaan dilakukan ketika semua sub-tugasnya dilakukan, dan masing-masing dilakukan ketika semua sub-operasinya selesai. Semua hal ini disimpan dalam database relasional. Panggilan tunggal ke fungsi lain dapat mengekstrak semua informasi itu, yangJobDone
disebut fungsi lain itu, melihat apakah pekerjaan itu dilakukan, dan membuang sisanya. Maka orang dapat dengan mudah menulis kode seperti ini:atau
Lihat intinya? Fungsinya sangat "kuat" dan mudah dipanggil sehingga terlalu banyak dipanggil. Jadi masalah kinerja bukan instruksi masuk dan keluar dari fungsi. Itu perlu ada cara yang lebih langsung untuk mengetahui apakah pekerjaan telah dilakukan. Sekali lagi, kode ini dapat tertanam dalam ribuan baris kode yang tidak bersalah. Mencoba memperbaikinya terlebih dahulu adalah apa yang semua orang coba lakukan, tapi itu seperti mencoba melemparkan anak panah di ruangan gelap. Yang Anda butuhkan adalah menjalankannya, dan kemudian biarkan "kode lambat" memberi tahu Anda apa itu, hanya dengan mengambil waktu. Untuk itu saya menggunakan jeda acak .
sumber
Saya pikir itu benar-benar tergantung pada bahasa dan fungsi. Sementara kompiler c dan c ++ dapat menyejajarkan banyak fungsi, ini bukan kasus untuk Python atau Java.
Meskipun saya tidak tahu rincian spesifik untuk java (kecuali bahwa setiap metode virtual tetapi saya menyarankan Anda untuk memeriksa dokumentasi dengan lebih baik), dengan Python saya yakin bahwa tidak ada inlining, tidak ada optimasi pengulangan ekor dan panggilan fungsi yang cukup mahal.
Fungsi-fungsi Python pada dasarnya adalah objek yang dapat dieksekusi (dan ternyata Anda juga dapat mendefinisikan metode panggilan () untuk membuat instance objek menjadi fungsi). Ini berarti ada cukup banyak overhead dalam memanggil mereka ...
TAPI
ketika Anda mendefinisikan variabel di dalam fungsi, interpreter menggunakan LOADFAST alih-alih instruksi LOAD normal dalam bytecode, membuat kode Anda lebih cepat ...
Hal lain adalah ketika Anda mendefinisikan objek yang dapat dipanggil, pola seperti memoisasi dimungkinkan dan mereka secara efektif dapat mempercepat perhitungan Anda (dengan biaya menggunakan lebih banyak memori). Pada dasarnya itu selalu merupakan trade off. Biaya fungsi panggilan juga tergantung pada parameter, karena mereka menentukan berapa banyak barang yang sebenarnya harus Anda salin di stack (sehingga dalam c / c ++ adalah praktik umum untuk melewatkan parameter besar seperti struktur dengan pointer / referensi alih-alih berdasarkan nilai).
Saya pikir pertanyaan Anda dalam praktik terlalu luas untuk dijawab sepenuhnya di stackexchange.
Apa yang saya sarankan Anda lakukan adalah mulai dengan satu bahasa dan mempelajari dokumentasi lanjutan untuk memahami bagaimana pemanggilan fungsi dilaksanakan oleh bahasa tertentu.
Anda akan terkejut dengan berapa banyak hal yang akan Anda pelajari dalam proses ini.
Jika Anda memiliki masalah khusus, lakukan pengukuran / profiling dan tentukan cuaca lebih baik untuk membuat fungsi atau menyalin / menempelkan kode yang setara.
jika Anda mengajukan pertanyaan yang lebih spesifik, saya pikir akan lebih mudah mendapatkan jawaban yang lebih spesifik.
sumber
Saya mengukur overhead panggilan fungsi C ++ langsung dan virtual pada Xenon PowerPC beberapa waktu lalu .
Fungsi-fungsi yang dimaksud memiliki parameter tunggal dan pengembalian tunggal, sehingga lewat parameter terjadi pada register.
Singkatnya, overhead panggilan fungsi langsung (non-virtual) adalah sekitar 5,5 nanodetik, atau siklus 18 jam, dibandingkan dengan panggilan fungsi sebaris. Overhead panggilan fungsi virtual adalah 13,2 nanodetik, atau 42 siklus clock, dibandingkan dengan inline.
Pengaturan waktu ini kemungkinan berbeda pada keluarga prosesor yang berbeda. Kode pengujian saya ada di sini ; Anda dapat menjalankan percobaan yang sama pada perangkat keras Anda. Gunakan timer presisi tinggi seperti rdtsc untuk implementasi CFastTimer Anda; waktu sistem () hampir tidak cukup tepat.
sumber