Manfaat fungsi sebaris di C ++?

254

Apa kelebihan / kekurangan menggunakan fungsi inline di C ++? Saya melihat bahwa itu hanya meningkatkan kinerja untuk kode yang dihasilkan oleh kompiler, tetapi dengan kompiler yang dioptimalkan saat ini, CPU cepat, memori besar dll. (Tidak seperti pada tahun 1980 <di mana memori langka dan semuanya harus masuk dalam memori 100KB) apa keuntungan yang mereka miliki saat ini?

Lennie De Villiers
sumber
48
Ini adalah salah satu pertanyaan di mana pengetahuan umum salah. Semua orang telah menjawab dengan jawaban Comp Sci standar. (Inlining menghemat biaya panggilan fungsi tetapi meningkatkan ukuran kode). Sampah. Ini menyediakan mekanisme sederhana bagi kompiler untuk menerapkan lebih banyak OPTIMISASI.
Martin York
37
Ini adalah salah satu jawaban yang diajukan sebagai komentar. Jika Anda tidak menyukai jawaban yang diposting, poskan jawaban Anda sendiri dan lihat bagaimana hasilnya.
Dave Van den Eynde
10
Dasar dari pertanyaan ini cacat. Fungsi-fungsi inline C ++ tidak ada hubungannya dengan kompiler yang digariskan selama kompilasi. Sangat disayangkan itu inlineadalah kata kunci c ++ dan inlining adalah teknik optimisasi kompiler. Lihat pertanyaan ini " kapan saya harus menulis kata kunci inlineuntuk fungsi / metode " untuk jawaban yang benar.
deft_code
3
@JoseVega Tautan Anda rusak - tautan saat ini adalah exforsys.com/tutorials/c-plus-plus/inline-functions.html

Jawaban:

143

Fungsi sebaris lebih cepat karena Anda tidak perlu mendorong dan memunculkan / mematikan tumpukan seperti parameter dan alamat pengirim; Namun, itu membuat biner Anda sedikit lebih besar.

Apakah itu membuat perbedaan yang signifikan? Tidak cukup terasa pada perangkat keras modern bagi kebanyakan orang. Tapi itu bisa membuat perbedaan, yang cukup bagi sebagian orang.

Menandai sesuatu yang sebaris tidak memberi Anda jaminan bahwa itu akan sejajar. Itu hanya saran untuk kompiler. Kadang-kadang itu tidak mungkin seperti ketika Anda memiliki fungsi virtual, atau ketika ada rekursi yang terlibat. Dan terkadang kompiler hanya memilih untuk tidak menggunakannya.

Saya bisa melihat situasi seperti ini membuat perbedaan yang dapat dideteksi:

inline int aplusb_pow2(int a, int b) {
  return (a + b)*(a + b) ;
}

for(int a = 0; a < 900000; ++a)
    for(int b = 0; b < 900000; ++b)
        aplusb_pow2(a, b);
Brian R. Bondy
sumber
26
Seperti yang saya duga inlining tidak ada bedanya dengan yang di atas. Dikompilasi dengan gcc 4.01. Versi 1 dipaksa untuk menggunakan inlining: 48.318u 1.042s 5: 51.39 99.4% 0 + 0k 0 + 0io 0pf + 0w Versi 2 dipaksa no inlining 348.311u 1.019s 5: 52.31 99.1% 0 + 0k 0 + 0io 0pf + 0w Ini contoh yang baik adalah pengetahuan umum salah.
Martin York
36
sementara panggilan itu sendiri memang penting, itu hanya keuntungan kecil yang Anda dapatkan dengan menggunakan inline. Keuntungan utama adalah, bahwa kompiler sekarang melihat di mana pointer tidak saling alias, di mana variabel penelepon berakhir di callee dan sebagainya. Jadi, pengoptimalan berikut ini yang lebih penting.
Johannes Schaub - litb
31
Ini mungkin tidak membuat perbedaan dalam cuplikan ini karena hasil dari fungsi tidak pernah digunakan atau apakah fungsi tersebut memiliki efek samping. Kami melihat peningkatan kinerja yang terukur dalam proses pengolahan gambar.
alas
4
Alasan untuk tidak ada perbedaan bisa jadi bahwa kompiler mungkin sebaris dengan sendirinya; atau bahwa kodenya kecil sehingga tidak ada masalah prefetch kode.
einpoklum
3
@einpoklum Kompiler bahkan mungkin telah mengoptimalkan seluruh loop karena itu.
noɥʇʎԀʎzɐɹƆ
197

Keuntungan

  • Dengan memasukkan kode Anda di tempat yang diperlukan, program Anda akan menghabiskan lebih sedikit waktu dalam pemanggilan fungsi dan mengembalikan komponen. Seharusnya membuat kode Anda berjalan lebih cepat, bahkan saat itu menjadi lebih besar (lihat di bawah). Memasukkan accessors yang sepele bisa menjadi contoh inlining yang efektif.
  • Dengan menandainya sebagai inline, Anda dapat menempatkan definisi fungsi dalam file header (yaitu dapat dimasukkan dalam beberapa unit kompilasi, tanpa mengeluh linker)

Kekurangan

  • Itu dapat membuat kode Anda lebih besar (yaitu jika Anda menggunakan inline untuk fungsi non-sepele). Dengan demikian, ini dapat memicu paging dan mengalahkan optimisasi dari kompiler.
  • Sedikit merusak enkapsulasi Anda karena mengekspos internal pemrosesan objek Anda (tetapi kemudian, setiap anggota "pribadi" juga akan). Ini berarti Anda tidak boleh menggunakan inlining dalam pola PImpl.
  • Ini sedikit merusak enkapsulasi Anda 2: C ++ inlining diselesaikan pada waktu kompilasi. Yang berarti bahwa jika Anda mengubah kode fungsi inline, Anda harus mengkompilasi ulang semua kode yang menggunakannya untuk memastikan kode itu akan diperbarui (untuk alasan yang sama, saya menghindari nilai default untuk parameter fungsi)
  • Ketika digunakan dalam header, itu membuat file header Anda lebih besar, dan dengan demikian, akan mencairkan informasi menarik (seperti daftar metode kelas) dengan kode yang tidak dipedulikan pengguna (ini adalah alasan mengapa saya mendeklarasikan fungsi yang digariskan di dalam sebuah kelas, tetapi akan mendefinisikannya di header setelah badan kelas, dan tidak pernah di dalam tubuh kelas).

Sihir Inlining

  • Kompiler mungkin atau tidak bisa inline fungsi yang Anda tandai sebagai inline; itu juga dapat memutuskan untuk fungsi sebaris tidak ditandai sebagai sebaris pada saat kompilasi atau menghubungkan waktu.
  • Inline berfungsi seperti salin / tempel yang dikendalikan oleh kompiler, yang sangat berbeda dari makro pra-prosesor: Makro akan digarisbawahi secara paksa, akan mencemari semua ruang nama dan kode, tidak akan mudah disangkal, dan akan dilakukan bahkan jika kompiler akan menganggapnya tidak efisien.
  • Setiap metode kelas yang didefinisikan di dalam tubuh kelas itu sendiri dianggap sebagai "inlined" (bahkan jika kompilator masih dapat memutuskan untuk tidak inline itu
  • Metode virtual tidak seharusnya tidak dapat ditiru. Namun, kadang-kadang, ketika kompiler dapat mengetahui dengan pasti jenis objek (yaitu objek dideklarasikan dan dibangun di dalam fungsi tubuh yang sama), bahkan fungsi virtual akan digarisbawahi karena kompiler tahu persis jenis objek.
  • Metode / fungsi templat tidak selalu digarisbawahi (kehadirannya di tajuk tidak akan membuatnya otomatis sebaris).
  • Langkah selanjutnya setelah "inline" adalah metaprograming template. Yaitu Dengan "menggarisbawahi" kode Anda pada waktu kompilasi, kadang-kadang, kompiler dapat menyimpulkan hasil akhir dari suatu fungsi ... Jadi algoritma yang kompleks kadang-kadang dapat direduksi menjadi semacam return 42 ;pernyataan. Ini bagi saya inlining ekstrim . Ini jarang terjadi dalam kehidupan nyata, itu membuat waktu kompilasi lebih lama, tidak akan mengasapi kode Anda, dan akan membuat kode Anda lebih cepat. Tapi seperti grail, jangan mencoba menerapkannya di mana-mana karena kebanyakan pemrosesan tidak dapat diselesaikan dengan cara ini ... Tetap saja, ini keren kok ...
    :-p
paercebal
sumber
Anda mengatakan itu sedikit merusak enkapsulasi Anda. Bisakah Anda jelaskan dengan menggunakan contoh?
Destructor
6
@PravasiMeet: Ini C ++. Katakanlah Anda mengirimkan pustaka DLL / bersama ke klien, yang mengkompilasinya. Fungsi inline foo, menggunakan variabel anggota X dan melakukan pekerjaan Y akan dimasukkan dalam kode klien. Katakanlah perlu memberikan versi DLL Anda yang diperbarui di mana Anda mengubah variabel anggota menjadi Z, dan menambahkan YY kerja di samping Y. Klien hanya menyalin DLL ke proyek mereka, dan BOOM, karena kode foo dalam biner mereka bukan kode yang diperbarui yang Anda tulis ... Meskipun klien tidak memiliki akses legal ke kode pribadi Anda, inlining membuatnya cukup "umum".
paercebal
@paercebal Mengenai poin kedua Anda terakhir, bisakah Anda memberikan contoh kapan templat fungsi tidak sejajar? Saya pikir mereka selalu sebaris, meskipun saya tidak memiliki referensi yang berguna sekarang (tes sederhana tampaknya mengkonfirmasikannya).
Konrad Rudolph
@KonradRudolph Dalam n4594 saya lihat: 3.2/6: There can be more than one definition of [..] inline function with external linkage [..] non-static function template. Di 5.1.5 / 6 For a generic lambda, the closure type has a public inline function call operator member template. Dan di 7.1.2/2: the use of inline keyword is to declare an inline functionmana itu adalah saran untuk menyelaraskan fungsi tubuh di titik panggilan. Jadi, saya menyimpulkan bahwa meskipun mereka dapat berperilaku sama, fungsi inline dan templat fungsi masih terpisah, gagasan ortogonal yang dapat dicampur (yaitu templat fungsi inline)
paercebal
enkapsulasi rusak? Bagaimana? enkapsulasi adalah untuk pemrograman pria, bukan untuk objek aktual dalam memori. pada saat itu tidak ada yang peduli. Bahkan jika Anda mendistribusikan perpustakaan, kompiler dapat memilih untuk sebaris atau tidak melakukannya sendiri. jadi pada akhirnya ketika Anda mendapatkan lib baru, Anda hanya perlu mengkompilasi ulang semua yang menggunakan fungsi dan objek dari perpustakaan itu.
FalcoGer
42

Dalam bahasa C dan C ++ kuno, inlineseperti register: sebuah saran (tidak lebih dari sebuah saran) kepada kompiler tentang kemungkinan optimasi.

Dalam C ++ modern, inlinememberi tahu linker bahwa, jika banyak definisi (bukan deklarasi) ditemukan dalam unit terjemahan yang berbeda, semuanya sama, dan linker dapat dengan bebas menyimpan satu dan membuang semua yang lain.

inline wajib jika suatu fungsi (tidak peduli seberapa kompleks atau "linier") didefinisikan dalam file header, untuk memungkinkan banyak sumber untuk memasukkannya tanpa mendapatkan kesalahan "beberapa definisi" oleh linker.

Fungsi anggota yang didefinisikan di dalam kelas adalah "inline" secara default, seperti fungsi templat (berbeda dengan fungsi global).

//fileA.h
inline void afunc()
{ std::cout << "this is afunc" << std::endl; }

//file1.cpp
#include "fileA.h"
void acall()
{ afunc(); }

//main.cpp
#include "fileA.h"
void acall();

int main()
{ 
   afunc(); 
   acall();
}

//output
this is afunc
this is afunc

Perhatikan penyertaan fileA.h ke dalam dua file .cpp, menghasilkan dua contoh afunc(). Tautan akan membuang salah satunya. Jika tidak inlineditentukan, penghubung akan mengeluh.

Emilio Garavaglia
sumber
16

Inlining adalah sebuah saran untuk kompiler yang bebas untuk diabaikan. Ini ideal untuk bit kode kecil.

Jika fungsi Anda sebaris, itu pada dasarnya dimasukkan dalam kode tempat pemanggilan fungsi dilakukan, alih-alih benar-benar memanggil fungsi yang terpisah. Ini dapat membantu dengan kecepatan karena Anda tidak harus melakukan panggilan yang sebenarnya.

Ini juga membantu CPU dalam pipelining karena mereka tidak perlu memuat ulang pipa dengan instruksi baru yang disebabkan oleh panggilan.

Satu-satunya kelemahan adalah kemungkinan peningkatan ukuran biner tetapi, selama fungsinya kecil, ini tidak terlalu menjadi masalah.

Saya cenderung menyerahkan keputusan-keputusan semacam ini kepada para penyusun saat ini (yah, yang pintar pula). Orang-orang yang menulisnya cenderung memiliki pengetahuan yang jauh lebih rinci tentang arsitektur yang mendasarinya.

paxdiablo
sumber
12

Fungsi sebaris adalah teknik optimisasi yang digunakan oleh kompiler. Satu hanya dapat menambahkan kata kunci inline ke prototipe fungsi untuk membuat fungsi inline. Fungsi inline menginstruksikan kompiler untuk memasukkan seluruh tubuh fungsi di mana pun fungsi itu digunakan dalam kode.

Keuntungan :-

  1. Tidak memerlukan fungsi panggilan overhead.

  2. Ini juga menghemat overhead variabel push / pop pada stack, sambil memanggil fungsi.

  3. Ini juga menghemat overhead panggilan balik dari suatu fungsi.

  4. Ini meningkatkan lokalitas referensi dengan menggunakan cache instruksi.

  5. Setelah in-lining, kompiler juga dapat menerapkan optimasi intra-prosedural jika ditentukan. Ini adalah yang paling penting, dengan cara ini kompiler sekarang dapat fokus pada penghapusan kode mati, dapat memberikan lebih banyak tekanan pada prediksi cabang, penghapusan variabel induksi dll.

Untuk mengetahui lebih lanjut tentang hal ini, seseorang dapat mengikuti tautan ini http://tajendrasengar.blogspot.com/2010/03/what-is-inline-function-in-cc.html

TSS
sumber
4
1) Ini saran, bukan instruksi 2) Ini dapat menyebabkan lebih banyak cache gagal karena peningkatan ukuran kode jika fungsi yang umum digunakan banyak digarisbawahi
Flexo
3
Apakah tautan di akhir blog pribadi Anda? Jika ya, Anda harus mendeklarasikannya seperti itu, jika tidak, itu terlihat seperti spam.
Flexo
6

Saya ingin menambahkan bahwa fungsi sebaris sangat penting ketika Anda sedang membangun perpustakaan bersama. Tanpa menandai fungsi inline, itu akan diekspor ke perpustakaan dalam bentuk biner. Ini juga akan hadir dalam tabel simbol, jika diekspor. Di sisi lain, fungsi sebaris tidak diekspor, baik ke biner perpustakaan maupun ke tabel simbol.

Mungkin kritis ketika perpustakaan dimaksudkan untuk dimuat saat runtime. Mungkin juga mengenai perpustakaan yang sadar biner-kompatibel. Dalam kasus seperti itu jangan gunakan inline.

dokter
sumber
@ Jonnsyweb: baca jawabanku dengan cermat. Apa yang Anda katakan itu benar, ketika Anda sedang membangun executable. Tetapi kompiler tidak bisa mengabaikan begitu saja inlineketika membangun perpustakaan bersama!
doc
4

Selama optimisasi, banyak kompiler akan inline fungsi bahkan jika Anda tidak menandainya. Anda biasanya hanya perlu menandai fungsi sebagai inline jika Anda tahu sesuatu yang tidak dikompilasi oleh kompiler, karena biasanya dapat membuat keputusan yang benar sendiri.

Devrin
sumber
Banyak kompiler juga tidak akan melakukan ini, MSVC misalnya tidak akan melakukan ini kecuali Anda menyuruhnya
paulm
4

inlinememungkinkan Anda untuk menempatkan definisi fungsi di file header dan #includefile header itu di beberapa file sumber tanpa melanggar aturan satu definisi.

Ferruccio
sumber
3

Secara umum, akhir-akhir ini dengan kompiler modern apa pun yang mengkhawatirkan inlining sesuatu cukup banyak membuang waktu. Kompiler harus benar-benar mengoptimalkan semua pertimbangan ini untuk Anda melalui analisis kode sendiri dan spesifikasi bendera pengoptimalan yang diteruskan ke kompiler. Jika Anda peduli tentang kecepatan, beri tahu kompiler untuk mengoptimalkan kecepatan. Jika Anda peduli ruang, beri tahu kompiler untuk mengoptimalkan ruang. Sebagai jawaban lain disinggung, kompiler yang layak bahkan akan otomatis inline jika itu benar-benar masuk akal.

Juga, seperti yang telah dinyatakan orang lain, menggunakan inline tidak menjamin inline apa pun. Jika Anda ingin menjaminnya, Anda harus mendefinisikan makro alih-alih fungsi sebaris untuk melakukannya.

Kapan harus inline dan / atau mendefinisikan makro untuk memaksa inklusi? - Hanya ketika Anda memiliki peningkatan yang terbukti dan perlu terbukti dalam kecepatan untuk bagian penting dari kode yang diketahui memiliki pengaruh pada kinerja keseluruhan aplikasi.

Jeff tinggi
sumber
... Jika Anda peduli ruang, beri tahu kompiler untuk mengoptimalkan ruang - memberitahu kompiler untuk mengoptimalkan kecepatan dapat menghasilkan biner yang lebih kecil dengan C ++ dan C. sama, memberi tahu kompiler untuk mengoptimalkan ruang dapat menghasilkan eksekusi yang lebih cepat. iow, fitur-fitur ini tidak selalu berfungsi seperti yang diiklankan. manusia memiliki kemampuan untuk memahami beberapa aspek dari program mereka lebih baik daripada interpretasi umum kompiler (yang juga harus tetap cepat). intervensi manusia tidak harus menjadi hal yang buruk.
justin
3

Ini bukan soal kinerja. Baik C ++ dan C digunakan untuk pemrograman tertanam, duduk di atas perangkat keras. Jika Anda akan, misalnya, menulis interrupt handler, Anda perlu memastikan bahwa kode dapat dieksekusi sekaligus, tanpa register tambahan dan / atau halaman memori sedang ditukar. Saat itulah inline berguna. Compiler yang baik melakukan beberapa "inlining" sendiri ketika kecepatan diperlukan, tetapi "inline" memaksa mereka.

JM Stoorvogel
sumber
1

Jatuh ke masalah yang sama dengan menguraikan fungsi ke dalam begitu banyak perpustakaan. Tampaknya fungsi yang digariskan tidak dikompilasi ke perpustakaan. sebagai akibatnya linker mengeluarkan kesalahan "referensi tidak terdefinisi", jika executable ingin menggunakan fungsi inline dari library. (Kebetulan saya mengkompilasi sumber Qt dengan gcc 4.5.

Martin Wilde
sumber
1

Mengapa tidak membuat semua fungsi sebaris secara default? Karena ini merupakan trade off teknik. Setidaknya ada dua jenis "optimasi": mempercepat program dan mengurangi ukuran (jejak memori) program. Inlining umumnya mempercepat. Itu menghilangkan fungsi panggilan overhead, menghindari mendorong lalu menarik parameter dari stack. Namun, ini juga membuat tapak memori program lebih besar, karena setiap pemanggilan fungsi sekarang harus diganti dengan kode lengkap fungsi. Untuk membuat segalanya semakin rumit, ingat bahwa CPU menyimpan potongan memori yang sering digunakan dalam cache pada CPU untuk akses ultra cepat. Jika Anda membuat gambar memori program cukup besar, program Anda tidak akan dapat menggunakan cache secara efisien, dan dalam kasus terburuk inlining sebenarnya dapat memperlambat program Anda.

mehrdad zomorodiyan
sumber
1

Profesor ilmu komputer kami mendesak kami untuk tidak pernah menggunakan inline dalam program c ++. Ketika ditanya mengapa, ia dengan ramah menjelaskan kepada kami bahwa kompiler modern harus mendeteksi kapan harus menggunakan inline secara otomatis.

Jadi ya, inline dapat menjadi teknik optimisasi untuk digunakan sedapat mungkin, tetapi tampaknya ini adalah sesuatu yang sudah dilakukan untuk Anda kapan pun dimungkinkan untuk inline suatu fungsi.

HumbleWebDev
sumber
5
Sayangnya profesor Anda benar-benar salah. inlinedi C ++ memiliki dua arti yang sangat berbeda - hanya satu yang terkait dengan optimasi, dan profesor Anda benar mengenai hal itu. Namun, makna kedua inlinesering diperlukan untuk memenuhi Aturan Satu Definisi .
Konrad Rudolph
-1

Kesimpulan dari diskusi lain di sini:

Apakah ada kekurangan dengan fungsi sebaris?

Ternyata, tidak ada yang salah dengan menggunakan fungsi inline.

Tetapi perlu diperhatikan hal-hal berikut!

  • Terlalu sering menggunakan inlining sebenarnya bisa membuat program lebih lambat. Bergantung pada ukuran fungsi, dengan menggariskannya dapat menyebabkan ukuran kode bertambah atau berkurang. Inlining fungsi pengakses yang sangat kecil biasanya akan mengurangi ukuran kode sementara inlining fungsi yang sangat besar dapat secara dramatis meningkatkan ukuran kode. Pada prosesor modern, kode yang lebih kecil biasanya berjalan lebih cepat karena penggunaan cache instruksi yang lebih baik. - Panduan Google

  • Manfaat kecepatan fungsi inline cenderung berkurang ketika fungsi tumbuh dalam ukuran. Di beberapa titik, overhead panggilan fungsi menjadi kecil dibandingkan dengan pelaksanaan fungsi tubuh, dan manfaatnya hilang - Sumber

  • Ada beberapa situasi di mana fungsi sebaris mungkin tidak berfungsi:

    • Untuk fungsi yang mengembalikan nilai; jika ada pernyataan pengembalian.
    • Untuk fungsi yang tidak mengembalikan nilai apa pun; jika ada loop, switch, atau statement goto.
    • Jika suatu fungsi bersifat rekursif. -Sumber
  • Kata __inlinekunci menyebabkan fungsi menjadi inline hanya jika Anda menentukan opsi optimisasi. Jika optimisasi ditentukan, apakah __inlinedihormati atau tidak tergantung pada pengaturan opsi pengoptimal sebaris. Secara default, opsi inline berlaku setiap kali optimizer dijalankan. Jika Anda menentukan pengoptimalan, Anda juga harus menentukan opsi noinline jika Anda ingin __inlinekata kunci diabaikan. -Sumber

prakash
sumber
3
Akan benar jika inline adalah perintah dan bukan petunjuk bagi kompiler. Compiler benar-benar memutuskan apa yang harus inline.
Martin York
1
@LokiAstari Saya tahu inline adalah permintaan untuk compiler. Argumen saya adalah Jika petunjuknya kepada kompiler kita harus menyerahkannya kepada kompiler untuk memutuskan mana yang terbaik. Mengapa menggunakan inline dengan cara apa pun, bahkan jika Anda menggunakan inline itu masih kompiler yang akan mengambil keputusan akhir. Saya juga ingin tahu bahwa Microsoft saya telah memperkenalkan _forceinline.
Krishna Oza
@krish_oza: Komentar saya di sini. Tentang jawaban ini. Jawabannya di sini benar-benar salah. Karena kompiler mengabaikan inlinekata kunci untuk menentukan apakah kode inline atau tidak semua poin yang dibuat di atas salah. Mereka akan benar jika kompiler menggunakan kata kunci untuk inlining (itu hanya digunakan untuk menentukan menandai beberapa definisi untuk tujuan menghubungkan).
Martin York