Fungsi sebaris dalam C #?

276

Bagaimana Anda melakukan "fungsi sebaris" di C #? Saya rasa saya tidak mengerti konsepnya. Apakah mereka suka metode anonim? Suka fungsi lambda?

Catatan : Jawabannya hampir seluruhnya berkaitan dengan kemampuan fungsi sebaris , yaitu "pengoptimalan manual atau kompiler yang menggantikan situs fungsi panggilan dengan tubuh callee." Jika Anda tertarik pada fungsi anonim (alias lambda) , lihat jawaban @ jalf atau Apa ini 'Lambda' yang semua orang bicarakan? .

Dina
sumber
11
Akhirnya mungkin - lihat jawaban saya.
konrad.kruczynski
1
Untuk hal terdekat sebelum .NET 4.5 lihat pertanyaan Cara mendefinisikan variabel sebagai fungsi lambda . Ini BUKAN sebenarnya mengkompilasi sebagai inline tetapi itu adalah deklarasi fungsi pendek seperti inline declaration. Tergantung pada apa yang ingin Anda capai dengan menggunakan inline.
AppFzx
Untuk orang yang penasaran, lihat ekstensi VS ini .
TripleAccretion

Jawaban:

384

Akhirnya di .NET 4.5, CLR memungkinkan seseorang untuk memberi petunjuk / menyarankan 1 metode inlining menggunakan MethodImplOptions.AggressiveInliningnilai. Ini juga tersedia di bagasi Mono (dilakukan hari ini).

// The full attribute usage is in mscorlib.dll,
// so should not need to include extra references
using System.Runtime.CompilerServices; 

...

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void MyMethod(...)

1 . Sebelumnya "kekuatan" digunakan di sini. Karena ada beberapa downvotes, saya akan mencoba mengklarifikasi istilah tersebut. Seperti dalam komentar dan dokumentasi, The method should be inlined if possible.Terutama mengingat Mono (yang terbuka), ada beberapa batasan teknis spesifik mono mempertimbangkan inlining atau yang lebih umum (seperti fungsi virtual). Secara keseluruhan, ya, ini adalah petunjuk untuk kompiler, tapi saya kira itulah yang diminta.

konrad.kruczynski
sumber
17
+1 - Diperbarui jawaban Anda untuk lebih spesifik tentang persyaratan versi kerangka kerja.
M.Babcock
4
Ini mungkin masih bukan inline kekuatan , tetapi mengesampingkan heuristik JITters sudah pasti cukup di sebagian besar situasi.
CodesInChaos
7
Pendekatan berbeda yang dapat bekerja dengan semua. Versi NET adalah untuk membagi metode yang agak terlalu besar menjadi dua metode, satu yang memanggil yang lain, yang keduanya tidak melebihi 32 byte IL. Efek bersihnya adalah seolah-olah yang asli itu sebaris.
Rick Sladkey
4
Itu tidak memaksa "Inlining" itu hanya mencoba untuk berbicara dengan JIT dan katakan bahwa programmer benar-benar ingin menggunakan Inlining di sini, tetapi JIT memiliki kata terakhir di dalamnya. Oleh karena itu MSDN: Metode ini harus diuraikan jika memungkinkan.
Orel Eraki
11
Sebagai perbandingan, saran inline C ++, bahkan yang khusus-compiler, juga tidak benar-benar memaksa inline: tidak semua fungsi dapat digarisbawahi (pada dasarnya hal-hal seperti fungsi rekursif sulit, tetapi ada kasus lain juga). Jadi ya, gaya "tidak-cukup" ini tipikal.
Eamon Nerbonne
87

Metode inline hanyalah pengoptimalan kompiler di mana kode fungsi digulirkan ke pemanggil.

Tidak ada mekanisme yang digunakan untuk melakukan ini dalam C #, dan mereka harus digunakan dengan hemat dalam bahasa di mana mereka didukung - jika Anda tidak tahu mengapa mereka harus digunakan di suatu tempat, mereka seharusnya tidak.

Sunting: Untuk memperjelas, ada dua alasan utama mereka perlu digunakan hemat:

  1. Sangat mudah untuk membuat biner besar dengan menggunakan inline jika tidak diperlukan
  2. Kompiler cenderung tahu lebih baik daripada yang Anda lakukan ketika sesuatu harus, dari sudut pandang kinerja, diuraikan

Yang terbaik adalah membiarkan hal-hal sendirian dan membiarkan kompiler melakukan tugasnya, kemudian profil dan cari tahu apakah inline adalah solusi terbaik untuk Anda. Tentu saja, beberapa hal masuk akal untuk digarisbawahi (operator matematika khususnya), tetapi membiarkan kompiler menanganinya biasanya merupakan praktik terbaik.

Cody Brocious
sumber
35
Biasanya saya pikir tidak apa-apa bahwa kompiler menangani inlining. Tetapi ada situasi di mana saya suka menimpa keputusan kompilator dan sebaris metode atau tidak.
mmmmmmmm
7
@ Joel Coehoorn: Ini akan menjadi praktik yang buruk karena akan merusak modularisasi dan perlindungan akses. (Pikirkan tentang metode inline dalam kelas yang mengakses anggota pribadi dan disebut dari sudut pandang yang berbeda dalam kode!)
mmmmmmmm
9
@ Poma Itu adalah alasan aneh untuk menggunakan inlining. Saya sangat ragu itu akan terbukti efektif.
Chris Shouts
56
Argumen tentang kompiler mengetahui yang terbaik adalah salah. Itu tidak sebaris metode apa pun yang lebih besar dari 32 byte IL, titik. Ini mengerikan untuk proyek-proyek (seperti milik saya, dan juga Egor di atas) yang memiliki hotspot yang diidentifikasi profiler yang tidak dapat kita lakukan sama sekali. Tidak ada, yaitu, kecuali untuk memotong dan menyisipkan kode dan secara manual sebaris. Singkatnya, ini adalah keadaan yang mengerikan ketika Anda benar-benar menggerakkan kinerja.
cerah
6
Inlining lebih halus dari yang terlihat. Memori memiliki cache yang cepat diakses, dan bagian kode saat ini disimpan dalam cache seperti halnya variabel. Memuat baris instruksi cache berikutnya adalah miss cache, dan biayanya mungkin 10 kali atau lebih dari apa yang dilakukan instruksi tunggal. Akhirnya harus memuat ke cache L2, yang bahkan lebih mahal. Dengan demikian, kode yang membengkak dapat menyebabkan lebih banyak cache yang terlewat, di mana fungsi inline yang tetap berada di garis cache L1 yang sama sepanjang waktu dapat mengakibatkan lebih sedikit cache yang hilang dan berpotensi kode yang lebih cepat. Dengan .Net itu bahkan lebih kompleks.
56

Pembaruan: Per jawaban konrad.kruczynski , berikut ini benar untuk versi .NET hingga dan termasuk 4.0.

Anda bisa menggunakan kelas MethodImplAttribute untuk mencegah metode tidak diuraikan ...

[MethodImpl(MethodImplOptions.NoInlining)]
void SomeMethod()
{
    // ...
}

... tetapi tidak ada cara untuk melakukan yang sebaliknya dan memaksanya untuk diuraikan.

DAGING BABI ASAP
sumber
2
Menarik untuk mengetahui hal itu, tetapi mengapa sih mencegah metode yang akan diuraikan? Saya menghabiskan beberapa waktu menatap monitor tetapi saya tidak dapat membuat alasan mengapa inlining dapat membahayakan.
Camilo Martin
3
Jika Anda menerima panggilan stack (yaitu NullReferenceException) dan jelas tidak ada cara bahwa metode di atas tumpukan melemparkannya. Tentu saja, salah satu panggilannya mungkin ada. Tapi yang mana?
dzendras
19
GetExecutingAssemblydan GetCallingAssemblydapat memberikan hasil yang berbeda tergantung pada apakah metode ini digarisbawahi. Memaksa suatu metode menjadi non-inline menghilangkan segala ketidakpastian.
stusmith
1
@Downvoter: jika Anda tidak setuju dengan apa yang saya tulis, Anda harus memposting komentar yang menjelaskan alasannya. Tidak hanya karena kesopanan umum, Anda juga tidak mencapai banyak hal dengan memberikan total 20+ jawaban.
BACON
2
Seandainya aku bisa +2 karena namamu saja layak diberi +1: D.
retrodrone
33

Anda mencampur dua konsep terpisah. Function inlining adalah pengoptimalan kompiler yang tidak berdampak pada semantik. Suatu fungsi berperilaku sama baik itu inline atau tidak.

Di sisi lain, fungsi lambda adalah murni konsep semantik. Tidak ada persyaratan tentang bagaimana mereka harus diimplementasikan atau dieksekusi, selama mereka mengikuti perilaku yang ditetapkan dalam spesifikasi bahasa. Mereka dapat diuraikan jika kompiler JIT terasa seperti itu, atau tidak jika tidak.

Tidak ada kata kunci inline dalam C #, karena ini merupakan optimasi yang biasanya dapat diserahkan kepada kompiler, terutama dalam bahasa JIT'ed. Kompiler JIT memiliki akses ke statistik runtime yang memungkinkannya untuk memutuskan apa yang harus digariskan jauh lebih efisien daripada yang Anda bisa saat menulis kode. Suatu fungsi akan digarisbawahi jika kompiler memutuskan, dan tidak ada yang dapat Anda lakukan dengan cara itu. :)

jalf
sumber
20
"Suatu fungsi berperilaku sama baik itu inline atau tidak." Ada beberapa kasus yang jarang terjadi di mana ini tidak benar: yaitu fungsi logging yang ingin tahu tentang jejak stack. Tapi saya tidak ingin merusak pernyataan dasar Anda terlalu banyak: itu umumnya benar.
Joel Coehoorn
"dan tidak ada yang bisa Anda lakukan dengan cara itu." - tidak benar. Sekalipun kita tidak menghitung "saran kuat" ke kompiler sebagai melakukan apa saja, Anda dapat mencegah fungsi agar tidak digarisbawahi.
BartoszKP
21

Apakah maksud Anda fungsi sebaris dalam arti C ++? Di mana isi fungsi normal secara otomatis disalin inline ke lokasi panggilan? Efek akhirnya adalah bahwa tidak ada panggilan fungsi yang sebenarnya terjadi saat memanggil fungsi.

Contoh:

inline int Add(int left, int right) { return left + right; }

Jika demikian maka tidak, tidak ada C # yang setara dengan ini.

Atau Apakah maksud Anda fungsi yang dideklarasikan dalam fungsi lain? Jika demikian maka ya, C # mendukung ini melalui metode anonim atau ekspresi lambda.

Contoh:

static void Example() {
  Func<int,int,int> add = (x,y) => x + y;
  var result = add(4,6);  // 10
}
JaredPar
sumber
21

Cody benar, tetapi saya ingin memberikan contoh apa fungsi inline.

Katakanlah Anda memiliki kode ini:

private void OutputItem(string x)
{
    Console.WriteLine(x);

    //maybe encapsulate additional logic to decide 
    // whether to also write the message to Trace or a log file
}

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{  // let's pretend IEnumerable<T>.ToList() doesn't exist for the moment
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);
        OutputItem(y);
    }
    return result;
}

The compiler Just-In-Time optimizer dapat memilih untuk mengubah kode untuk menghindari berulang kali menempatkan panggilan ke OutputItem () pada stack, sehingga akan menjadi seperti jika Anda telah menulis kode seperti ini sebagai gantinya:

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);

        // full OutputItem() implementation is placed here
        Console.WriteLine(y);   
    }

    return result;
}

Dalam hal ini, kita akan mengatakan fungsi OutputItem () diuraikan. Perhatikan bahwa itu mungkin melakukan ini bahkan jika OutputItem () dipanggil dari tempat lain juga.

Diedit untuk menunjukkan skenario yang lebih mungkin untuk digarisbawahi.

Joel Coehoorn
sumber
9
Hanya untuk memperjelas; JIT-lah yang melakukan inlining; bukan kompiler C #.
Marc Gravell
Juga perhatikan bahwa, pada awalnya setidaknya, JIT akan 'lebih suka' untuk metode statis inline, bahkan melintasi batas-batas perakitan. Jadi, teknik kuno untuk optimisasi adalah menandai metode Anda sebagai statis. Ini sejak itu disukai oleh masyarakat tetapi sampai hari ini saya masih akan memilih metode inline yang bersifat internal, non-polimorfik dan biasanya lebih murah untuk dieksekusi daripada tumpukan tumpahan + tumpahan yang terkait.
Shaun Wilson
7

Ya Tepat, satu-satunya perbedaan adalah fakta mengembalikan nilai.

Penyederhanaan (tidak menggunakan ekspresi):

List<T>.ForEach Mengambil tindakan, itu tidak mengharapkan hasil pengembalian.

Jadi seorang Action<T>delegasi akan cukup .. katakan:

List<T>.ForEach(param => Console.WriteLine(param));

sama dengan mengatakan:

List<T>.ForEach(delegate(T param) { Console.WriteLine(param); });

perbedaannya adalah bahwa tipe param dan deklarasi delegasi disimpulkan oleh penggunaan dan kawat gigi tidak diperlukan pada metode inline sederhana.

Dimana sebagai

List<T>.Where Mengambil fungsi, mengharapkan hasil.

Jadi yang Function<T, bool>diharapkan:

List<T>.Where(param => param.Value == SomeExpectedComparison);

yang sama dengan:

List<T>.Where(delegate(T param) { return param.Value == SomeExpectedComparison; });

Anda juga dapat mendeklarasikan metode ini secara inline dan menandainya dengan variabel IE:

Action myAction = () => Console.WriteLine("I'm doing something Nifty!");

myAction();

atau

Function<object, string> myFunction = theObject => theObject.ToString();

string myString = myFunction(someObject);

Saya harap ini membantu.

Quintin Robinson
sumber
2

Ada saat-saat di mana saya ingin memaksakan kode dimasukkan.

Sebagai contoh jika saya memiliki rutinitas yang kompleks di mana ada sejumlah besar keputusan yang dibuat dalam blok yang sangat iteratif dan keputusan itu menghasilkan tindakan yang serupa tetapi sedikit berbeda untuk dilakukan. Pertimbangkan misalnya, pembanding pengurutan yang kompleks (non-driven DB) di mana algorythm pengurutan mengurutkan elemen berdasarkan sejumlah kriteria yang tidak terkait seperti yang mungkin dilakukan jika mereka mengurutkan kata-kata berdasarkan kriteria gramatik dan semantik untuk bahasa cepat. sistem pengenalan. Saya akan cenderung menulis fungsi pembantu untuk menangani tindakan tersebut untuk menjaga keterbacaan dan modularitas kode sumber.

Saya tahu bahwa fungsi-fungsi pembantu itu harus dimasukkan karena itulah cara kode akan ditulis jika tidak pernah harus dipahami oleh manusia. Saya pasti ingin memastikan dalam hal ini bahwa tidak ada fungsi yang memanggil overhead.

pengembang
sumber
2

Pernyataan "yang terbaik adalah membiarkan hal-hal ini sendirian dan membiarkan kompiler melakukan pekerjaan .." (Cody Brocious) benar-benar sia-sia. Saya telah memprogram kode permainan berkinerja tinggi selama 20 tahun, dan saya belum menemukan kompiler yang 'cukup pintar' untuk mengetahui kode mana yang harus digariskan (fungsi) atau tidak. Akan berguna untuk memiliki pernyataan "inline" dalam c #, kebenarannya adalah bahwa kompiler tidak memiliki semua informasi yang diperlukan untuk menentukan fungsi mana yang harus selalu diuraikan atau tidak tanpa petunjuk "inline". Yakin jika fungsinya kecil (accessor) maka itu mungkin secara otomatis diuraikan, tetapi bagaimana jika itu adalah beberapa baris kode? Nonesense, kompiler tidak memiliki cara untuk mengetahui, Anda tidak bisa membiarkannya sampai ke kompiler untuk kode yang dioptimalkan (di luar algoritma).

gvick2002
sumber
3
"Nonesense, kompiler tidak memiliki cara untuk mengetahui" JITer melakukan profil waktu panggilan fungsi secara real-time
Brian Gordon
3
.NET JIT telah dikenal untuk mengoptimalkan pada saat run-time lebih baik daripada apa yang mungkin dengan analisis statis 2-pass pada waktu kompilasi. Bagian sulit yang harus dipahami oleh para pembuat kode "old school" adalah bahwa JIT, bukan kompiler, yang bertanggung jawab untuk pembuatan kode asli. JIT, bukan kompiler, bertanggung jawab untuk metode inline.
Shaun Wilson
1
Mungkin bagi saya untuk mengkompilasi kode yang Anda panggil, tanpa kode sumber saya, dan JIT dapat memilih untuk inline panggilan. Biasanya saya akan setuju, tetapi saya akan mengatakan sebagai manusia Anda tidak lagi bisa melampaui alat.
Shaun Wilson
0

Tidak, tidak ada konstruksi seperti itu di C #, tetapi .NET JIT compiler dapat memutuskan untuk melakukan panggilan fungsi inline pada waktu JIT. Tetapi saya sebenarnya tidak tahu apakah itu benar-benar melakukan optimasi seperti itu.
(Saya pikir seharusnya :-))

mmmmmmmm
sumber
0

Jika rakitan Anda akan ngen-ed, Anda mungkin ingin melihat di TargetedPatchingOptOut. Ini akan membantu ngen memutuskan apakah akan menggunakan metode inline. Referensi MSDN

Ini masih hanya petunjuk deklaratif untuk mengoptimalkan, bukan perintah imperatif.

Tim Lovell-Smith
sumber
Dan JIT akan tahu lebih baik, apakah akan inline atau tidak. Jelas tidak akan membantu Anda jika JIT tidak mengoptimalkan ruang panggilan, tapi lalu mengapa saya harus khawatir tentang overhead minimal dari fungsi yang disebut hanya beberapa kali?
Voo
-7

Ekspresi Lambda adalah fungsi sebaris! Saya pikir, bahwa C # tidak memiliki atribut tambahan seperti inline atau sesuatu seperti itu!

cordellcp3
sumber
9
Saya tidak menurunkan suara Anda, tetapi harap baca pertanyaannya dan pastikan Anda memahaminya sebelum mengirim jawaban acak. en.wikipedia.org/wiki/Inline_function
Camilo Martin
1
Jawaban ini tidak seburuk yang telah dibuat - OP tidak jelas tentang 'fungsi inlining' vs 'fungsi lambda', dan sayangnya MSDN menyebut lambda sebagai "pernyataan inline" atau "kode inline" . Namun, sesuai jawaban Konrad, ada atribut untuk mengisyaratkan kepada kompiler tentang inlining suatu metode.
StuartLC
-7

C # tidak mendukung metode sebaris (atau fungsi) dengan cara bahasa dinamis seperti python lakukan. Namun metode anonim dan lambda dapat digunakan untuk tujuan yang sama termasuk ketika Anda perlu mengakses variabel dalam metode yang mengandung seperti pada contoh di bawah ini.

static void Main(string[] args)
{
    int a = 1;

    Action inline = () => a++;
    inline();
    //here a = 2
}
Yordan Pavlov
sumber
10
Apa hubungannya dengan fungsi sebaris? ini adalah metode Anonim.
Tomer W