Funkcje wbudowane w języku C #?


276

Jak wykonywać „funkcje wbudowane” w języku C #? Nie sądzę, że rozumiem tę koncepcję. Czy są jak anonimowe metody? Jak funkcje lambda?

Uwaga : odpowiedzi prawie w całości dotyczą możliwości wstawiania funkcji , tj. „Optymalizacji ręcznej lub kompilatora, która zastępuje witrynę wywołania funkcji ciałem odbiorcy”. Jeśli interesują Cię anonimowe (aka lambda) funkcje , zobacz odpowiedź @ jalfa lub o czym jest ta „Lambda”, o której wszyscy mówią? .


11
W końcu jest to możliwe - zobacz moją odpowiedź.
konrad.kruczynski

1
Najbliższe rzeczy przed .NET 4.5 patrz pytanie Jak zdefiniować zmienną jako funkcję lambda . W rzeczywistości NIE kompiluje się jako wbudowany, ale jest to krótka deklaracja funkcji, taka jak deklaracja wbudowana. Zależy od tego, co próbujesz osiągnąć za pomocą wbudowanego.
AppFzx

Odpowiedzi:


384

Wreszcie w .NET 4.5 CLR pozwala wskazać / zasugerować wprowadzenie 1 metody za pomocą MethodImplOptions.AggressiveInliningwartości. Jest on również dostępny w bagażniku Mono (zatwierdzony dzisiaj).

// 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 . Wcześniej używano tutaj „siły”. Ponieważ było kilka głosów negatywnych, postaram się wyjaśnić ten termin. Podobnie jak w komentarzach i dokumentacji, The method should be inlined if possible.szczególnie biorąc pod uwagę Mono (który jest otwarty), istnieją pewne techniczne ograniczenia mono-specyficzne, biorąc pod uwagę wbudowanie lub bardziej ogólne (takie jak funkcje wirtualne). Ogólnie tak, jest to wskazówka dla kompilatora, ale myślę, że o to poproszono.


17
+1 - Zaktualizowałem twoją odpowiedź, aby była bardziej szczegółowa na temat wymagań wersji ramowej.
M.Babcock

4
Prawdopodobnie nadal nie jest to siła wbudowana, ale w większości sytuacji wystarczająca jest heurystyka JITters.
CodesInChaos

7
Inne podejście, które może działać ze wszystkimi wersjami .NET, polega na podzieleniu nieco zbyt dużej metody na dwie metody, z których jedna wywołuje drugą, z których żadna nie przekracza 32 bajtów IL. Efekt netto jest taki, jakby oryginał był wstawiony.
Rick Sladkey,

4
To nie wymusza „Inliningu”, po prostu spróbuj porozmawiać z JIT i powiedzieć, że programista naprawdę chce tu użyć Inliningu, ale JIT ma na to ostatnie słowo. Stąd MSDN: W miarę możliwości należy wprowadzić metodę.
Orel Eraki

11
Dla porównania, wbudowane sugestie C ++, nawet te specyficzne dla kompilatora, również nie wymuszają wstawiania: nie wszystkie funkcje można wstawiać (zasadniczo rzeczy takie jak funkcje rekurencyjne są trudne, ale są też inne przypadki). Tak, ta „niezupełnie” siła jest typowa.
Eamon Nerbonne,

87

Metody inline to po prostu optymalizacja kompilatora, w której kod funkcji jest wprowadzany do programu wywołującego.

Nie ma mechanizmu, dzięki któremu można to zrobić w języku C #, i powinny być używane oszczędnie w językach, w których są obsługiwane - jeśli nie wiesz, dlaczego powinny być gdzieś używane, nie powinny.

Edycja: Aby wyjaśnić, istnieją dwa główne powody, dla których należy je stosować oszczędnie:

  1. Tworzenie dużych plików binarnych jest łatwe przy użyciu wbudowania w przypadkach, gdy nie jest to konieczne
  2. Kompilator zwykle wie lepiej niż ty, kiedy coś z punktu widzenia wydajności powinno być podkreślone

Najlepiej zostawić wszystko w spokoju i pozwolić kompilatorowi wykonać swoją pracę, a następnie profilować i zastanowić się, czy inline jest najlepszym rozwiązaniem dla Ciebie. Oczywiście, niektóre rzeczy mają sens, aby je wprowadzić (szczególnie operatory matematyczne), ale pozwolenie kompilatorowi na obsługę tego jest zwykle najlepszą praktyką.


35
Zwykle uważam, że kompilator obsługuje wstawianie. Ale są sytuacje, w których lubię zastępować decyzję kompilatora i wstawiać lub nie wstawiać metody.
mmmmmmmm

7
@Jel Coehoorn: Byłaby to zła praktyka, ponieważ złamałaby modularyzację i ochronę dostępu. (Pomyśl o wprowadzonej metodzie w klasie, która uzyskuje dostęp do prywatnych członków i jest wywoływana z innego miejsca w kodzie!)
mmmmmmmm

9
@Poma To dziwny powód, by używać inliningu. Wątpię, czy okaże się to skuteczne.
Chris Shouts

56
Argumenty na temat najlepszej wiedzy kompilatora są po prostu błędne. Nie wstawia żadnej metody większej niż 32 bajty IL, kropka. Jest to okropne dla projektów (takich jak mój, a także Egora powyżej), które mają identyfikowane przez profil punkty aktywne, z którymi nie możemy zrobić absolutnie nic. Nic, tylko wycinanie i wklejanie kodu, a więc ręczne wstawianie. Jednym słowem jest to okropny stan rzeczy, gdy naprawdę napędzasz osiągi.
jasny

6
Inlining jest bardziej subtelny, niż się wydaje. Pamięć ma pamięci podręczne, do których dostęp jest szybki, a bieżąca część kodu jest przechowywana w pamięci podręcznej, podobnie jak zmienne. Ładowanie kolejnej linii instrukcji pamięci podręcznej to brak pamięci podręcznej i może kosztować 10 lub więcej razy więcej niż jedna instrukcja. W końcu musi się załadować do pamięci podręcznej L2, która jest jeszcze droższa. W ten sposób nadęty kod może powodować pominięcie większej ilości pamięci podręcznej, a wbudowana funkcja, która pozostaje rezydentna w tych samych liniach pamięci podręcznej L1 przez cały czas, może spowodować mniej błędów pamięci podręcznej i potencjalnie szybszy kod. Z .Net jest jeszcze bardziej złożony.

56

Aktualizacja: Zgodnie z odpowiedzią konrad.kruczynski , poniższe są prawdziwe dla wersji .NET do wersji 4.0 włącznie.

Możesz użyć metody MethodImplAttribute, aby zapobiec wstawianiu metody ...

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

... ale nie ma sposobu, aby zrobić coś przeciwnego i zmusić go do podkreślenia.


2
Interesujące, aby to wiedzieć, ale dlaczego do cholery nie można wprowadzić metody? Spędziłem trochę czasu, wpatrując się w monitor, ale nie mogę zrozumieć, dlaczego inlining może wyrządzić jakąkolwiek krzywdę.
Camilo Martin,

3
Jeśli otrzymasz stos wywołań (np. NullReferenceException) i oczywiście nie ma możliwości, aby metoda na szczycie stosu go rzuciła. Oczywiście jedno z jego połączeń może mieć. Ale który?
dzendras,

19
GetExecutingAssemblyi GetCallingAssemblymoże dawać różne wyniki w zależności od tego, czy metoda jest wbudowana. Wymuszenie braku wbudowania metody eliminuje wszelką niepewność.
stusmith,

1
@Downvoter: jeśli nie zgadzasz się z tym, co napisałem, powinieneś zamieścić komentarz wyjaśniający dlaczego. Nie tylko jest to powszechna uprzejmość, ale również nie osiągasz wiele, odkładając głos na ponad 20 odpowiedzi.
BACON,

2
Żałuję, że nie mogę +2, ponieważ twoje imię zasługuje na +1: D.
retrodron

33

Mieszacie dwie odrębne koncepcje. Inlining funkcji to optymalizacja kompilatora, która nie ma wpływu na semantykę. Funkcja zachowuje się tak samo, niezależnie od tego, czy jest wstawiana, czy nie.

Z drugiej strony funkcje lambda są czysto semantycznym pojęciem. Nie ma wymagań dotyczących sposobu ich implementacji lub wykonania, pod warunkiem, że zachowują się one zgodnie ze specyfikacją języka. Można je wstawić, jeśli kompilator JIT ma na to ochotę, lub nie, jeśli tak nie jest.

W języku C # nie ma wbudowanego słowa kluczowego, ponieważ jest to optymalizacja, którą zwykle można pozostawić kompilatorowi, szczególnie w językach JIT. Kompilator JIT ma dostęp do statystyk środowiska wykonawczego, co pozwala mu decydować, co wstawiać znacznie wydajniej niż jest to możliwe podczas pisania kodu. Funkcja zostanie wstawiona, jeśli kompilator podejmie taką decyzję, i nic nie możesz na to poradzić. :)


20
„Funkcja zachowuje się tak samo, niezależnie od tego, czy jest wbudowana, czy nie.” Istnieje kilka rzadkich przypadków, w których nie jest to prawdą: mianowicie funkcje rejestrowania, które chcą wiedzieć o śledzeniu stosu. Ale nie chcę zbyt mocno podważać twojego podstawowego stwierdzenia: jest to na ogół prawda.
Joel Coehoorn

„i nic nie możesz na to poradzić.” - nieprawda. Nawet jeśli nie liczymy „silnej sugestii” dla kompilatora jako robienia czegokolwiek, możesz zapobiec wstawianiu funkcji.
BartoszKP

21

Masz na myśli funkcje wbudowane w sensie C ++? W którym zawartość normalnej funkcji jest automatycznie kopiowana bezpośrednio do strony wywoławczej? Efektem końcowym jest to, że podczas wywoływania funkcji nie dzieje się żadne wywołanie funkcji.

Przykład:

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

Jeśli tak, to nie, nie ma C # równoważnego z tym.

Czy masz na myśli funkcje zadeklarowane w ramach innej funkcji? Jeśli tak, to tak, C # obsługuje to za pomocą anonimowych metod lub wyrażeń lambda.

Przykład:

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

21

Cody ma rację, ale chcę podać przykład tego, czym jest funkcja wbudowana.

Powiedzmy, że masz ten kod:

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;
}

Kompilator Just-In-Time optymalizator mogli wybrać, aby zmienić kod wielokrotnie unikać umieszczania wezwanie do OutputItem () na stosie, tak, że będzie tak, jakbyś napisał kod jak to w zamian:

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;
}

W takim przypadku powiedzielibyśmy, że funkcja OutputItem () została wstawiona. Zauważ, że może to zrobić, nawet jeśli OutputItem () jest wywoływany również z innych miejsc.

Edytowane, aby pokazać scenariusz, który może zostać wstawiony.


9
Tylko dla wyjaśnienia; to JIT wykonuje inlining; nie kompilator C #.
Marc Gravell

Zauważ też, że przynajmniej początkowo JIT „wolałby” wstawiać metody statyczne, nawet ponad granicami zespołu. Tak więc oldschoolową techniką optymalizacji było oznaczanie metod jako statycznych. Od tamtej pory społeczność nie cieszy się tym, ale do dziś będę decydować się na metody inline, które mają charakter wewnętrzny, niepolimorficzny i zwykle kosztują mniej niż wykonanie związane z wypełnieniem stosu + rozlaniem.
Shaun Wilson

7

Tak Dokładnie, jedynym rozróżnieniem jest fakt, że zwraca wartość.

Uproszczenie (bez użycia wyrażeń):

List<T>.ForEach Podejmuje akcję, nie oczekuje zwrotu.

Tak więc Action<T>wystarczyłby delegat ... powiedz:

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

to to samo co powiedzenie:

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

Różnica polega na tym, że typ parametru i decelacja delegata są wywnioskowane na podstawie użycia, a nawiasy klamrowe nie są wymagane w prostej metodzie inline.

Natomiast

List<T>.Where Pełni funkcję, oczekując wyniku.

Więc Function<T, bool>można by się spodziewać:

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

który jest taki sam jak:

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

Możesz również zadeklarować te metody bezpośrednio i przypisać je do zmiennych IE:

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

myAction();

lub

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

string myString = myFunction(someObject);

Mam nadzieję, że to pomoże.


2

Są sytuacje, w których chcę wymusić wstawienie kodu.

Na przykład, jeśli mam złożoną procedurę, w której w bardzo iteracyjnym bloku podejmowanych jest wiele decyzji, które skutkują podobnymi, ale nieco różniącymi się działaniami do wykonania. Rozważmy na przykład złożony moduł porównujący sortowanie (nie oparty na bazie danych), w którym algorytm sortujący sortuje elementy według szeregu różnych niepowiązanych kryteriów, takich jak na przykład sortowanie słów według kryteriów gramatycznych i semantycznych szybkiego języka system rozpoznawania. Miałem tendencję do pisania funkcji pomocniczych do obsługi tych akcji w celu utrzymania czytelności i modułowości kodu źródłowego.

Wiem, że te funkcje pomocnicze powinny być wbudowane, ponieważ w taki sposób kod zostałby napisany, gdyby człowiek nigdy nie musiał go rozumieć. Z pewnością chciałbym w tym przypadku upewnić się, że nie będzie narzutów wywołujących funkcji.


2

Stwierdzenie „najlepiej zostawić te rzeczy w spokoju i pozwolić kompilatorowi wykonać pracę ...” (Cody Brocious) jest kompletnym rubinem. Programuję wysokowydajny kod do gry od 20 lat i jeszcze nie spotkałem się z kompilatorem, który jest „wystarczająco inteligentny”, aby wiedzieć, który kod należy wstawić (funkcje), czy nie. Przydałoby się mieć instrukcję „inline” w języku c #, prawda jest taka, że ​​kompilator po prostu nie ma wszystkich informacji potrzebnych do ustalenia, która funkcja powinna być zawsze wstawiana, czy nie bez wskazówki „inline”. Jasne, że jeśli funkcja jest mała (akcesor), może być automatycznie wstawiana, ale co, jeśli jest to kilka wierszy kodu? Bez sensu, kompilator nie ma możliwości wiedzieć, nie możesz po prostu zostawić tego kompilatorowi w zakresie zoptymalizowanego kodu (poza algorytmami).


3
„Bez sensu, kompilator nie ma pojęcia” JITer wykonuje profilowanie wywołań funkcji w czasie rzeczywistym.
Brian Gordon

3
Wiadomo, że .NET JIT optymalizuje się w czasie wykonywania lepiej niż jest to możliwe dzięki dwustopniowej analizie statycznej w czasie kompilacji. Trudne do zrozumienia przez programistów „oldschoolowych” jest to, że JIT, a nie kompilator, jest odpowiedzialny za generowanie kodu natywnego. JIT, a nie kompilator, jest odpowiedzialny za metody wbudowane.
Shaun Wilson

1
Możliwe jest, że skompiluję kod, do którego dzwonisz, bez mojego kodu źródłowego, a JIT może zdecydować się na wstawienie połączenia. Normalnie zgodziłbym się, ale powiedziałbym, że jako człowiek nie jesteś już w stanie przewyższyć narzędzi.
Shaun Wilson


0

Nie, nie ma takiej konstrukcji w języku C #, ale kompilator .NET JIT może zdecydować o wykonywaniu wbudowanych funkcji w czasie JIT. Ale tak naprawdę nie wiem, czy naprawdę robi takie optymalizacje.
(Myślę, że powinien :-))


0

W przypadku, gdy twoje zespoły będą ngenowane, możesz rzucić okiem na TargetedPatchingOptOut. Pomoże to Ngen zdecydować, czy wstawić metody.Odniesienie MSDN

Jest to jednak tylko deklaratywna wskazówka do optymalizacji, a nie polecenie nakazowe.


A JIT i tak będzie wiedział znacznie lepiej, czy inline, czy nie. Oczywiście nie pomoże ci, jeśli JIT nie zoptymalizuje strony wywoławczej, ale dlaczego, do diabła, miałbym się martwić minimalnym narzutem funkcji, która jest wywoływana tylko kilka razy?
Voo,

-7

Wyrażenia lambda są funkcjami wbudowanymi! Myślę, że C # nie ma dodatkowego atrybutu takiego jak inline lub coś takiego!


9
Nie głosowałem za tobą, ale przeczytaj pytania i upewnij się, że je rozumiesz, zanim opublikujesz losową odpowiedź. en.wikipedia.org/wiki/Inline_function
Camilo Martin

1
Ta odpowiedź nie jest tak zła, jak się okazało - PO nie jest jasne, czy „funkcja inlininguje” w porównaniu z „funkcjami lambda”, i niestety MSDN określa lambdas jako „instrukcje wbudowane” lub „kod wbudowany” . Jednak, zgodnie z odpowiedzią Konrada, istnieje atrybut wskazujący kompilatorowi na wprowadzenie metody.
StuartLC

-7

C # nie obsługuje wbudowanych metod (lub funkcji) w sposób, w jaki robią to dynamiczne języki, takie jak Python. Jednak anonimowych metod i lambd można używać do podobnych celów, w tym gdy trzeba uzyskać dostęp do zmiennej w metodzie zawierającej, jak w poniższym przykładzie.

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

    Action inline = () => a++;
    inline();
    //here a = 2
}

10
Co to ma wspólnego z funkcjami wbudowanymi? to jest metoda anonimowa.
Tomer W
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.