Fungsi inline v. Makro di C Berapa Overhead (Memori/Kecepatan)?

Saya menelusuri Stack Overflow untuk mengetahui kelebihan/kekurangan fungsi makro v. fungsi inline.

Saya menemukan diskusi berikut: Pro dan Kontra dari Fungsi makro/metode sebaris yang berbeda di C

...tapi itu tidak menjawab pertanyaan utama saya.

Yaitu, berapa overhead di c dari penggunaan fungsi makro (dengan variabel, kemungkinan pemanggilan fungsi lain) v. fungsi inline, dalam hal penggunaan memori dan kecepatan eksekusi?

Apakah ada perbedaan overhead yang bergantung pada kompiler? Saya memiliki icc dan gcc yang dapat saya gunakan.

Cuplikan kode yang saya modularkan adalah:

double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = AttractiveTerm * AttractiveTerm;
EnergyContribution += 
   4 * Epsilon * (RepulsiveTerm - AttractiveTerm);

Alasan saya untuk mengubahnya menjadi fungsi/makro inline adalah agar saya dapat memasukkannya ke dalam file c dan kemudian mengkompilasi fungsi/makro lain yang serupa, tetapi sedikit berbeda secara kondisional.

e.g.:

double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3);
double RepulsiveTerm = pow(SigmaSquared/RadialDistanceSquared,9);
EnergyContribution += 
   4 * Epsilon * (RepulsiveTerm - AttractiveTerm);

(perhatikan perbedaannya pada baris kedua...)

Fungsi ini adalah inti dari kode saya dan dipanggil ribuan kali per langkah dalam program saya dan program saya melakukan jutaan langkah. Jadi saya ingin memiliki overhead yang SEDANG mungkin, oleh karena itu mengapa saya membuang-buang waktu mengkhawatirkan overhead inlining v. mengubah kode menjadi makro.

Berdasarkan diskusi sebelumnya saya sudah menyadari pro/kontra lainnya (kemandirian tipe dan kesalahan yang dihasilkan darinya) makro... tapi yang paling ingin saya ketahui, dan saat ini tidak saya ketahui adalah KINERJA.

Saya tahu beberapa dari Anda para veteran C akan memiliki wawasan yang luar biasa untuk saya!!


person Jason R. Mick    schedule 07.03.2011    source sumber
comment
Gunakan profiler, lalu putuskan.   -  person Erik    schedule 08.03.2011
comment
Tidak ada jawaban umum?   -  person Jason R. Mick    schedule 08.03.2011
comment
+1 untuk tautan ke beranda stackoverflow :) LOL   -  person 0x90    schedule 28.05.2013
comment
Mendapat peningkatan kinerja 100%+ dengan mengonversi beberapa fungsi validasi karakter menjadi varian MACRO() di parser tempat fungsi ini sering dipanggil. inline dan __forceinline hanya mendapat peningkatan 50% dibandingkan panggilan fungsi biasa tetapi MACROs menghasilkan 100%. (melacak kinerja dalam siklus) - Jadi menurut saya, jika fungsi inline pendek dan sederhana, cobalah makro dan benchmark.   -  person CodeAngry    schedule 05.04.2014
comment
Kemungkinan duplikat Fungsi sebaris vs makro Praprosesor   -  person Jim Fell    schedule 09.08.2017


Jawaban (9)


Memanggil fungsi inline mungkin menghasilkan atau tidak menghasilkan pemanggilan fungsi, yang biasanya menimbulkan biaya overhead yang sangat kecil. Situasi sebenarnya di mana fungsi inline benar-benar dimasukkan bervariasi tergantung pada kompilernya; sebagian besar melakukan upaya dengan itikad baik untuk menyejajarkan fungsi-fungsi kecil (setidaknya ketika pengoptimalan diaktifkan), tetapi tidak ada persyaratan bahwa mereka melakukannya (C99, ยง6.7.4):

Menjadikan suatu fungsi sebagai fungsi inline menyarankan agar panggilan ke fungsi tersebut dilakukan secepat mungkin. Sejauh mana saran-saran tersebut efektif tergantung pada implementasinya.

Makro cenderung tidak menimbulkan overhead seperti itu (walaupun sekali lagi, hanya ada sedikit hal yang dapat mencegah kompiler melakukan sesuatu; standar tidak menentukan apa yang harus diperluas oleh program kode mesin, hanya perilaku yang dapat diamati dari program yang dikompilasi).

Gunakan apa pun yang lebih bersih. Profil. Jika itu penting, lakukan sesuatu yang berbeda.

Juga, apa yang fizzer katakan; panggilan ke kekuatan (dan divisi) keduanya biasanya lebih mahal daripada overhead panggilan fungsi. Meminimalkan hal tersebut adalah awal yang baik:

double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += 4 * Epsilon * AttractiveTerm * (AttractiveTerm - 1.0);

Apakah EnergyContribution hanya terdiri dari istilah-istilah yang terlihat seperti ini? Jika ya, tarik 4 * Epsilon keluar, dan simpan dua perkalian per iterasi:

double ratio = SigmaSquared/RadialDistanceSquared;
double AttractiveTerm = ratio*ratio*ratio;
EnergyContribution += AttractiveTerm * (AttractiveTerm - 1.0);
// later, once you've done all of those terms...
EnergyContribution *= 4 * Epsilon;
person Stephen Canon    schedule 08.03.2011

Makro sebenarnya bukan sebuah fungsi. apa pun yang Anda definisikan sebagai makro akan diposting kata demi kata ke dalam kode Anda, sebelum kompiler melihatnya, oleh praprosesor. Praprosesor hanyalah alat insinyur perangkat lunak yang memungkinkan berbagai abstraksi untuk menyusun kode Anda dengan lebih baik.

Suatu fungsi sebaris atau sebaliknya yang diketahui oleh kompiler, dan dapat membuat keputusan tentang apa yang harus dilakukan dengannya. Kata kunci inline yang disediakan pengguna hanyalah sebuah saran dan kompiler dapat menimpanya. Over-riding inilah yang dalam banyak kasus akan menghasilkan kode yang lebih baik.

Efek samping lain jika kompiler menyadari fungsinya adalah Anda berpotensi memaksa kompiler mengambil keputusan tertentu -misalnya, menonaktifkan penyisipan kode, yang memungkinkan Anda melakukan debug atau membuat profil kode dengan lebih baik. Mungkin ada banyak kasus penggunaan lain yang mengaktifkan fungsi inline vs. makro.

Makro sangatlah kuat, dan untuk mendukung hal ini saya akan mengutip google test dan google mock. Ada banyak alasan untuk menggunakan makro :D.

Operasi matematika sederhana yang dirangkai bersama menggunakan fungsi sering kali digariskan oleh kompiler, terutama jika fungsi tersebut hanya dipanggil satu kali pada langkah penerjemahan. Jadi, saya tidak akan terkejut jika kompiler mengambil keputusan sebaris untuk Anda, terlepas dari cuaca kata kunci tersebut diberikan atau tidak.

Namun, jika kompiler tidak melakukannya, Anda dapat meratakan segmen kode Anda secara manual. Jika Anda meratakannya mungkin makro akan berfungsi sebagai abstraksi yang baik, lagipula makro menyajikan semantik yang mirip dengan fungsi "nyata".

Intinya

Jadi, apakah Anda ingin kompiler menyadari batasan logis tertentu sehingga dapat menghasilkan kode fisik yang lebih baik, atau Anda ingin memaksakan keputusan pada kompiler dengan meratakannya secara manual atau dengan menggunakan makro. Industri ini condong ke arah yang pertama.

Saya akan cenderung menggunakan makro dalam hal ini, hanya karena cepat dan kotor, tanpa harus belajar lebih banyak. Namun, karena makro adalah abstraksi rekayasa perangkat lunak, dan karena Anda peduli dengan kode yang dihasilkan kompiler, jika masalahnya menjadi sedikit lebih lanjut saya akan menggunakan templat C++, karena templat tersebut dirancang untuk masalah yang Anda renungkan.

person Hassan Syed    schedule 08.03.2011

Itu adalah panggilan ke pow() yang ingin Anda hilangkan. Fungsi ini menggunakan eksponen floating point umum dan tidak efisien untuk menaikkan ke eksponen integral. Mengganti panggilan ini dengan mis.

inline double cube(double x)
{
    return x * x * x;
}

adalah satu-satunya hal yang akan membuat perbedaan signifikan terhadap kinerja Anda di sini.

person fizzer    schedule 08.03.2011
comment
Dengan kompiler C++ modern, bukankah panggilan seperti pow(x, integer) akan disederhanakan menjadi apa yang Anda gambarkan, daripada menggunakan panggilan yang lebih mahal? - person Tyler Shellberg; 02.11.2019

Makro, termasuk makro yang mirip fungsi, adalah substitusi teks sederhana, dan dengan demikian dapat merugikan Anda jika Anda tidak benar-benar berhati-hati dengan parameter Anda. Misalnya, makro SQUARE yang sangat populer:

#define SQUARE(x) ((x)*(x))

bisa menjadi bencana yang menunggu untuk terjadi jika Anda menyebutnya sebagai SQUARE(i++). Selain itu, makro yang mirip fungsi tidak memiliki konsep ruang lingkup, dan tidak mendukung variabel lokal; peretasan paling populer adalah sesuatu seperti

#define MACRO(S,R,E,C)                                     \
do                                                         \   
{                                                          \
  double AttractiveTerm = pow((S)/(R),3);                  \
  double RepulsiveTerm = AttractiveTerm * AttractiveTerm;  \
  (C) = 4 * (E) * (RepulsiveTerm - AttractiveTerm);        \
} while(0)

yang tentu saja mempersulit penetapan hasil seperti x = MACRO(a,b);.

Taruhan terbaik dari sudut pandang kebenaran dan pemeliharaan adalah menjadikannya sebuah fungsi dan menentukan inline. Makro bukanlah fungsi dan berbeda dengan makro.

Setelah Anda selesai melakukannya, ukur kinerjanya dan temukan di mana letak hambatan sebenarnya sebelum meretasnya (panggilan ke pow tentu saja akan menjadi kandidat untuk penyederhanaan).

person John Bode    schedule 08.03.2011

Harap tinjau standar pengkodean CERT Secure yang berbicara tentang makro dan fungsi inline dalam hal keamanan dan munculnya bug, saya tidak menganjurkan penggunaan makro seperti fungsi, karena: - Profil Kurang - Kurang Dapat Dilacak - Lebih sulit untuk di-debug - Dapat Menyebabkan Bug parah

person Muhammed Abdul Galeil    schedule 11.10.2012

Cara terbaik untuk menjawab pertanyaan Anda adalah dengan membandingkan kedua pendekatan tersebut untuk melihat mana yang sebenarnya lebih cepat di aplikasi Anda, menggunakan data pengujian Anda. Prediksi mengenai kinerja sangat tidak dapat diandalkan kecuali pada tingkat yang paling kasar.

Meskipun demikian, saya perkirakan tidak akan ada perbedaan yang signifikan antara makro dan pemanggilan fungsi yang benar-benar sebaris. Dalam kedua kasus tersebut, Anda akan mendapatkan kode perakitan yang sama.

person Eric Melski    schedule 08.03.2011
comment
Maafkan pengalaman saya -- apa yang Anda maksud dengan fungsi yang benar-benar sebaris? Bukankah semua fungsi yang dideklarasikan dengan kata kunci inline diusahakan untuk disisipkan? - person Jason R. Mick; 08.03.2011
comment
@Jason: tidak, tidak. Mengutip standar: Menjadikan suatu fungsi sebagai fungsi inline menyarankan agar panggilan ke fungsi tersebut dilakukan secepat mungkin. Sejauh mana saran-saran tersebut efektif tergantung pada implementasinya. - person Stephen Canon; 08.03.2011
comment
Kata kunci inline hanyalah saran untuk kompiler. Ini mungkin sejalan atau mungkin tidak. - person JayM; 08.03.2011

Jika Anda jeda acak ini, yang mungkin Anda lihat adalah 100% (dikurangi epsilon) waktu ada di dalam fungsi pow, jadi cara mencapainya pada dasarnya tidak membuat perbedaan.

Dengan asumsi Anda menemukannya, hal pertama yang harus dilakukan adalah menghilangkan panggilan ke pow yang Anda temukan di tumpukan. (Secara umum, yang dilakukannya adalah mengambil log dari argumen pertama, mengalikannya dengan argumen kedua, dan exp dari argumen tersebut, atau sesuatu yang menghasilkan hal yang sama. log dan exp dapat dilakukan dengan semacam rangkaian yang melibatkan banyak aritmatika. Ini tentu saja mencari kasus-kasus khusus, tapi itu masih akan memakan waktu lebih lama dari yang Anda inginkan.) Itu saja sudah bisa memberi Anda kecepatan yang besarnya.

Kemudian lakukan jeda acak lagi. Sekarang Anda akan melihat hal lain yang menghabiskan banyak waktu. Saya tidak bisa menebak apa yang akan terjadi, begitu pula orang lain, tapi Anda mungkin bisa menguranginya juga. Lakukan terus sampai Anda tidak bisa melakukannya lagi.

Ini mungkin terjadi saat Anda memilih untuk menggunakan makro, dan mungkin sedikit lebih cepat daripada fungsi sebaris. Itu bagi Anda untuk menilai ketika Anda sampai di sana.

person Mike Dunlavey    schedule 08.03.2011

seperti yang dikatakan orang lain, ini sebagian besar bergantung pada kompiler.

Saya yakin "pow" menghabiskan biaya lebih banyak daripada inlining atau makro mana pun yang akan menghemat Anda :)

Saya pikir lebih bersih jika ini merupakan fungsi inline daripada makro.

caching dan pipelining benar-benar merupakan tempat Anda akan mendapatkan keuntungan besar jika Anda menjalankan ini pada prosesor modern. yaitu. hapus pernyataan bercabang seperti 'jika' membuat perbedaan besar (dapat dilakukan dengan sejumlah trik)

person Keith Nicholas    schedule 08.03.2011

Seperti yang saya pahami dari beberapa orang yang menulis kompiler, setelah Anda memanggil suatu fungsi dari dalam, kemungkinan besar kode Anda tidak akan dimasukkan. Tapi, itulah mengapa sebaiknya Anda tidak menggunakan makro. Makro menghapus informasi dan meninggalkan kompiler dengan pilihan yang jauh lebih sedikit untuk dioptimalkan. Dengan kompiler multi-pass dan pengoptimalan seluruh program, mereka akan mengetahui bahwa memasukkan kode Anda akan menyebabkan prediksi cabang yang gagal atau kehilangan cache atau kekuatan sihir hitam lainnya yang digunakan CPU modern untuk bekerja dengan cepat. Saya pikir semua orang benar untuk menunjukkan bahwa kode di atas tidak optimal, jadi disitulah fokusnya seharusnya.

person Tavison    schedule 26.03.2011