Funzioni incorporate in C #?


276

Come si fanno le "funzioni incorporate" in C #? Non credo di capire il concetto. Sono come metodi anonimi? Ti piacciono le funzioni lambda?

Nota : le risposte riguardano quasi interamente la capacità di incorporare le funzioni , ovvero "un'ottimizzazione manuale o del compilatore che sostituisce un sito di chiamata di funzione con il corpo della chiamata". Se sei interessato a funzioni anonime (aka lambda) , vedi la risposta di @ jalf o Di cosa parla questo 'Lambda'? .


11
Finalmente è possibile - vedi la mia risposta.
konrad.kruczynski,

1
Per la cosa più vicina a .NET 4.5 vedi domanda Come definire la variabile come funzione lambda . In realtà NON si sta compilando come inline ma è una breve dichiarazione di funzione come la dichiarazione inline. Dipende da ciò che stai cercando di ottenere utilizzando inline.
AppFzx,

Risposte:


384

Infine in .NET 4.5, il CLR consente di suggerire / suggerire 1 metodo inline usando il MethodImplOptions.AggressiveInliningvalore. È disponibile anche nel bagagliaio del Mono (impegnato oggi).

// 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 . Precedentemente qui veniva usata la "forza". Dato che c'erano alcuni voti negativi, cercherò di chiarire il termine. Come nei commenti e nella documentazione, The method should be inlined if possible.Soprattutto considerando Mono (che è aperto), ci sono alcune limitazioni tecniche mono-specifiche che considerano inline o più generali (come le funzioni virtuali). Nel complesso, sì, questo è un suggerimento per il compilatore, ma immagino che sia quello che è stato chiesto.


17
+1: aggiornata la risposta per essere più specifici sui requisiti della versione del framework.
M.Babcock,

4
Probabilmente non è ancora una forza in linea, ma scavalcare l'euristica di JITters è certamente abbastanza nella maggior parte delle situazioni.
CodesInChaos,

7
Un approccio diverso che può funzionare con tutte le versioni di .NET è quello di dividere un metodo leggermente troppo grande in due metodi, uno che chiama l'altro, nessuno dei quali supera i 32 byte di IL. L'effetto netto è come se l'originale fosse in linea.
Rick Sladkey,

4
Non sta forzando "Inlining", è solo provare a parlare con JIT e dirgli che il programmatore vuole davvero usare Inlining qui, ma JIT ha l'ultima parola su di esso. Quindi MSDN: il metodo dovrebbe essere incorporato se possibile.
Orel Eraki,

11
In confronto, anche i suggerimenti in linea di C ++, anche quelli specifici del compilatore, non impongono effettivamente una in linea: non tutte le funzioni possono essere integrate (fondamentalmente cose come le funzioni ricorsive sono difficili, ma ci sono anche altri casi). Quindi sì, questa forza "non del tutto" è tipica.
Eamon Nerbonne,

87

I metodi inline sono semplicemente un'ottimizzazione del compilatore in cui il codice di una funzione viene inserito nel chiamante.

Non esiste un meccanismo per farlo in C # e devono essere usati con parsimonia nelle lingue in cui sono supportati - se non sai perché dovrebbero essere usati da qualche parte, non dovrebbero esserlo.

Modifica: per chiarire, ci sono due motivi principali che devono essere usati con parsimonia:

  1. È facile creare enormi binari usando inline nei casi in cui non è necessario
  2. Il compilatore tende a sapere meglio di te quando qualcosa dovrebbe, dal punto di vista delle prestazioni, essere sottolineato

È meglio lasciare le cose da solo e lasciare che il compilatore faccia il suo lavoro, quindi profilare e capire se inline è la soluzione migliore per te. Certo, alcune cose hanno semplicemente senso essere allineate (in particolare operatori matematici), ma lasciare che il compilatore lo gestisca è in genere la migliore pratica.


35
Normalmente penso che sia OK che il compilatore gestisca l'allineamento. Ma ci sono situazioni in cui mi piace ignorare la decisione del compilatore e incorporare o meno un metodo.
mmmmmmmm,

7
@Joel Coehoorn: sarebbe una cattiva pratica perché spezzerebbe la modularizzazione e la protezione dell'accesso. (Pensa a un metodo integrato in una classe che accede a membri privati ​​e viene chiamato da un altro punto del codice!)
mmmmmmmm

9
@Poma Questa è una strana ragione per usare l'allineamento. Dubito fortemente che si dimostrerebbe efficace.
Chris Shouts

56
Gli argomenti sul compilatore che conosce meglio sono sbagliati. Non incorpora alcun metodo maggiore di 32 byte IL, punto. Questo è orribile per progetti (come il mio, e anche quello di Egor sopra) che hanno hotspot identificati dal profiler di cui non possiamo assolutamente fare nulla. Nulla, tranne che per tagliare e incollare il codice e così manualmente in linea. Questo è, in una parola, un terribile stato di cose quando sei davvero alla guida delle prestazioni.
luminoso

6
L'allineamento è più sottile di quanto sembri. La memoria ha cache a cui è possibile accedere rapidamente e la parte corrente del codice è memorizzata in quelle cache proprio come lo sono le variabili. Il caricamento della riga di istruzioni della cache successiva comporta un mancato funzionamento della cache e costa forse 10 o più volte rispetto a una singola istruzione. Alla fine deve caricarsi nella cache L2, che è ancora più costosa. Pertanto, il codice gonfiato può causare la perdita di più cache, in cui una funzione incorporata che rimane residente nelle stesse linee della cache L1 per tutto il tempo potrebbe comportare un minor numero di errori nella cache e un codice potenzialmente più veloce. Con .Net è ancora più complesso.

56

Aggiornamento: Per la risposta di konrad.kruczynski , quanto segue è vero per le versioni di .NET fino ae includendo 4.0.

È possibile utilizzare la classe MethodImplAttribute per evitare che un metodo venga inline ...

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

... ma non c'è modo di fare il contrario e costringerlo ad essere allineato.


2
Interessante saperlo, ma perché diamine impedisce che un metodo sia integrato? Ho trascorso un po 'di tempo a fissare il monitor, ma non riesco a capire il motivo per cui l'allineamento potrebbe fare del male.
Camilo Martin,

3
Se ricevi uno stack di chiamate (cioè NullReferenceException) e ovviamente non è possibile che sia stato lanciato dal metodo in cima allo stack. Certo, una delle sue chiamate potrebbe avere. Ma quale?
dzendras,

19
GetExecutingAssemblye GetCallingAssemblypuò dare risultati diversi a seconda che il metodo sia inline. Costringere un metodo a non essere incorporato elimina ogni incertezza.
stusmith

1
@Downvoter: se non sei d'accordo con quello che ho scritto, dovresti pubblicare un commento che spieghi il perché. Non solo è una cortesia comune, ma non si ottiene molto anche il punteggio totale di una risposta di 20+.
BACON,

2
Vorrei poter fare +2 perché il tuo nome da solo merita +1: D.
retrodrone

33

Stai mescolando due concetti separati. L'allineamento delle funzioni è un'ottimizzazione del compilatore che non ha alcun impatto sulla semantica. Una funzione si comporta allo stesso modo sia che sia incorporata o meno.

D'altra parte, le funzioni lambda sono puramente un concetto semantico. Non è richiesto il modo in cui devono essere implementati o eseguiti, purché seguano il comportamento indicato nelle specifiche del linguaggio. Possono essere integrati se il compilatore JIT ne ha voglia o no.

Non esiste una parola chiave incorporata in C #, perché è un'ottimizzazione che di solito può essere lasciata al compilatore, specialmente nei linguaggi JIT. Il compilatore JIT ha accesso alle statistiche di runtime che gli consentono di decidere cosa incorporare in modo molto più efficiente di quanto è possibile quando si scrive il codice. Una funzione verrà sottolineata se il compilatore decide, e non c'è niente che tu possa fare al riguardo. :)


20
"Una funzione si comporta allo stesso modo sia che sia incorporata o meno." Ci sono alcuni rari casi in cui ciò non è vero: vale a dire le funzioni di registrazione che vogliono conoscere la traccia dello stack. Ma non voglio minare troppo la tua affermazione di base: è generalmente vero.
Joel Coehoorn,

"e non c'è niente che tu possa fare al riguardo in entrambi i modi." - Non è vero. Anche se non stiamo contando il "suggerimento forte" per il compilatore come fare qualsiasi cosa, è possibile impedire che le funzioni vengano incorporate.
BartoszKP,

21

Vuoi dire funzioni in linea in senso C ++? In quale contenuto di una normale funzione viene automaticamente copiato in linea nel sito di chiamata? L'effetto finale è che nessuna chiamata di funzione si verifica effettivamente quando si chiama una funzione.

Esempio:

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

In tal caso, no, non esiste C # equivalente a questo.

Oppure intendi funzioni dichiarate all'interno di un'altra funzione? In tal caso, sì, C # supporta questo tramite metodi anonimi o espressioni lambda.

Esempio:

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

21

Cody ha ragione, ma voglio fornire un esempio di cosa sia una funzione in linea.

Supponiamo che tu abbia questo codice:

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

L' ottimizzatore Just-In-Time del compilatore potrebbe scegliere di modificare il codice per evitare di effettuare ripetutamente una chiamata a OutputItem () nello stack, in modo che sia come se invece avessi scritto il codice in questo modo:

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

In questo caso, diremmo che la funzione OutputItem () era inline. Nota che potrebbe farlo anche se OutputItem () viene chiamato anche da altre posizioni.

Modificato per mostrare uno scenario più probabile da includere.


9
Giusto per chiarire però; è la squadra che fa l'allineamento; non il compilatore C #.
Marc Gravell

Si noti inoltre che, almeno inizialmente, la SIC preferirebbe "incorporare" i metodi statici, anche oltre i limiti dell'assembly. Pertanto, una tecnica di vecchia scuola per l'ottimizzazione consisteva nel contrassegnare i metodi come statici. Da allora questo è stato disapprovato dalla comunità, ma fino ad oggi continuerò a optare per metodi in linea che sono interni, di natura non polimorfica e in genere costano meno da eseguire rispetto allo stack fill + spill associato.
Shaun Wilson,

7

Sì Esatto, l'unica distinzione è il fatto che restituisce un valore.

Semplificazione (non usando espressioni):

List<T>.ForEach Intraprende un'azione, non prevede un risultato di ritorno.

Quindi un Action<T>delegato basterebbe .. dire:

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

è come dire:

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

la differenza è che il tipo di parametro e la declerazione del delegato sono dedotti dall'uso e le parentesi graffe non sono richieste su un metodo inline semplice.

Mentre

List<T>.Where Accetta una funzione, in attesa di un risultato.

Quindi Function<T, bool>ci si aspetterebbe:

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

che è lo stesso di:

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

Puoi anche dichiarare questi metodi in linea e assegnarli alle variabili IE:

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

myAction();

o

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

string myString = myFunction(someObject);

Spero che aiuti.


2

Ci sono occasioni in cui desidero forzare l'inserimento del codice.

Ad esempio, se ho una routine complessa in cui vi è un gran numero di decisioni prese all'interno di un blocco altamente iterativo e tali decisioni si traducono in azioni simili ma leggermente diverse da eseguire. Si consideri ad esempio un comparatore di ordinamento complesso (non basato su DB) in cui l'algoritmo di ordinamento ordina gli elementi in base a una serie di diversi criteri non correlati come si potrebbe fare se si ordinassero le parole in base a criteri grammaticali e semantici per un linguaggio veloce sistema di riconoscimento. Tenderei a scrivere funzioni di supporto per gestire tali azioni al fine di mantenere la leggibilità e la modularità del codice sorgente.

So che quelle funzioni di supporto dovrebbero essere allineate perché è così che il codice verrebbe scritto se non dovesse mai essere compreso da un essere umano. Vorrei certamente assicurarmi in questo caso che non vi fosse alcuna funzione di chiamata ambientale.


2

L'affermazione "è meglio lasciare queste cose da sole e lasciare che il compilatore faccia il lavoro .." (Cody Brocious) è un rubish completo. Sto programmando un codice di gioco ad alte prestazioni da 20 anni e devo ancora imbattermi in un compilatore che è "abbastanza intelligente" per sapere quale codice dovrebbe essere incorporato (funzioni) o meno. Sarebbe utile avere un'istruzione "inline" in c #, la verità è che il compilatore non ha tutte le informazioni necessarie per determinare quale funzione dovrebbe essere sempre incorporata o meno senza il suggerimento "inline". Sicuro se la funzione è piccola (accessor) potrebbe essere automaticamente incorporata, ma cosa succede se si tratta di poche righe di codice? Non ha senso, il compilatore non ha modo di saperlo, non puoi lasciarlo al compilatore per un codice ottimizzato (oltre gli algoritmi).


3
"Non ha senso, il compilatore non ha modo di saperlo" Il JITer esegue il profiling in tempo reale delle chiamate di funzione ..
Brian Gordon,

3
.NET JIT è noto per l'ottimizzazione in fase di esecuzione meglio di quanto sia possibile con un'analisi statica a 2 passaggi in fase di compilazione. La parte difficile da comprendere per i programmatori della "vecchia scuola" è che la JIT, non il compilatore, è responsabile della generazione del codice nativo. JIT, non il compilatore, è responsabile dei metodi inline.
Shaun Wilson,

1
È possibile per me compilare il codice in cui si chiama, senza il mio codice sorgente, e JIT può scegliere di incorporare la chiamata. Normalmente sarei d'accordo, ma direi che come essere umano non sei più in grado di superare gli strumenti.
Shaun Wilson,


0

No, non esiste tale costrutto in C #, ma il compilatore .NET JIT potrebbe decidere di eseguire chiamate di funzione inline al momento JIT. Ma in realtà non so se stia davvero facendo tali ottimizzazioni.
(Penso che dovrebbe :-))


0

Nel caso in cui i tuoi assiemi vengano modificati, potresti dare un'occhiata a TargetedPatchingOptOut. Ciò aiuterà ngen a decidere se includere metodi. Riferimento MSDN

È comunque solo un suggerimento dichiarativo da ottimizzare, non un comando imperativo.


E la squadra saprà molto meglio se inline o no. Ovviamente non ti aiuterà se JIT non ottimizza il sito di chiamata, ma allora perché diavolo dovrei preoccuparmi dell'overhead minimo di una funzione chiamata solo poche volte?
Voo,

-7

Le espressioni lambda sono funzioni incorporate! Penso che C # non abbia un attributo extra come inline o qualcosa del genere!


9
Non ti ho sottovalutato, ma per favore leggi le domande e assicurati di capirle prima di pubblicare una risposta casuale. en.wikipedia.org/wiki/Inline_function
Camilo Martin l'

1
Questa risposta non è così negativa come è stata fatta - l'OP non è chiaro riguardo a 'funzione inlining' vs 'funzioni lambda', e sfortunatamente MSDN si riferisce a lambdas come "istruzioni inline" o "codice inline" . Tuttavia, secondo la risposta di Konrad, esiste un attributo che suggerisce al compilatore di includere un metodo.
StuartLC,

-7

C # non supporta i metodi (o le funzioni) incorporati come fanno i linguaggi dinamici come Python. Tuttavia, metodi anonimi e lambda possono essere utilizzati per scopi simili, anche quando è necessario accedere a una variabile nel metodo di contenimento, come nell'esempio seguente.

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

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

10
Cosa c'entra questo con le funzioni in linea? questo è un metodo anonimo.
Tomer W,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.