Fonctions en ligne en C #?

276

Comment faites-vous des "fonctions en ligne" en C #? Je ne pense pas comprendre le concept. Sont-ils comme des méthodes anonymes? Comme les fonctions lambda?

Remarque : Les réponses traitent presque entièrement de la capacité à incorporer des fonctions , c'est-à-dire "une optimisation manuelle ou de compilation qui remplace un site d'appel de fonction par le corps de l'appelé". Si vous êtes intéressé par les fonctions anonymes (aka lambda) , voir la réponse de @ jalf ou de quoi parle cette 'Lambda' dont tout le monde parle? .

Dinah
la source
11
C'est enfin possible - voir ma réponse.
konrad.kruczynski
1
Pour la chose la plus proche avant .NET 4.5, voir la question Comment définir une variable en tant que fonction lambda . Ce n'est PAS réellement compiler en ligne mais c'est une déclaration de fonction courte comme la déclaration en ligne. Dépend de ce que vous essayez de réaliser en utilisant en ligne.
AppFzx
Pour les curieux, consultez cette extension VS .
TripleAccretion

Réponses:

384

Enfin dans .NET 4.5, le CLR permet de suggérer / suggérer 1 méthode en ligne en utilisant la MethodImplOptions.AggressiveInliningvaleur. Il est également disponible dans le coffre du Mono (engagé aujourd'hui).

// 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 . Auparavant, la «force» était utilisée ici. Puisqu'il y a eu quelques downvotes, je vais essayer de clarifier le terme. Comme dans les commentaires et la documentation, en The method should be inlined if possible.particulier en ce qui concerne Mono (qui est ouvert), il existe certaines limitations techniques spécifiques à mono en ce qui concerne l'inclusion ou plus générale (comme les fonctions virtuelles). Dans l'ensemble, oui, c'est un indice pour le compilateur, mais je suppose que c'est ce qui a été demandé.

konrad.kruczynski
la source
17
+1 - Mise à jour de votre réponse pour être plus précis sur les exigences de version du framework.
M.Babcock
4
Ce n'est probablement pas une force en ligne, mais remplacer l'heuristique JITters est certainement suffisant dans la plupart des situations.
CodesInChaos
7
Une approche différente qui peut fonctionner avec toutes les versions de .NET consiste à diviser une méthode légèrement trop volumineuse en deux méthodes, l'une qui appelle l'autre, dont aucune ne dépasse 32 octets d'IL. L'effet net est comme si l'original était en ligne.
Rick Sladkey
4
Ce n'est pas forcer le "Inlining" c'est juste essayer de parler avec le JIT et lui dire que le programmeur veut vraiment utiliser Inlining ici, mais le JIT a le dernier mot dessus. D'où MSDN: la méthode doit être intégrée si possible.
Orel Eraki
11
Par comparaison, les suggestions en ligne de C ++, même celles spécifiques au compilateur, ne forcent pas réellement une ligne en ligne: toutes les fonctions ne peuvent pas être en ligne (fondamentalement des choses comme les fonctions récursives sont difficiles, mais il y a aussi d'autres cas). Alors oui, cette force "pas tout à fait" est typique.
Eamon Nerbonne
87

Les méthodes en ligne sont simplement une optimisation du compilateur où le code d'une fonction est roulé dans l'appelant.

Il n'y a pas de mécanisme pour le faire en C #, et ils doivent être utilisés avec parcimonie dans les langages où ils sont pris en charge - si vous ne savez pas pourquoi ils devraient être utilisés quelque part, ils ne devraient pas l'être.

Edit: Pour clarifier, il y a deux raisons principales pour lesquelles ils doivent être utilisés avec parcimonie:

  1. Il est facile de créer des binaires massifs en utilisant inline dans les cas où cela n'est pas nécessaire
  2. Le compilateur a tendance à mieux connaître que vous lorsque quelque chose devrait, du point de vue des performances, être aligné

Il est préférable de laisser les choses tranquilles et de laisser le compilateur faire son travail, puis de profiler et de déterminer si l'inline est la meilleure solution pour vous. Bien sûr, certaines choses ont simplement du sens d'être intégrées (opérateurs mathématiques en particulier), mais laisser le compilateur gérer cela est généralement la meilleure pratique.

Cody Brocious
la source
35
Normalement, je pense que c'est OK que le compilateur gère l'inline. Mais il y a des situations où j'aime écraser la décision du compilateur et incorporer ou non une méthode.
mmmmmmmm
7
@Joel Coehoorn: Ce serait une mauvaise pratique car cela casserait la modularisation et la protection d'accès. (Pensez à une méthode intégrée dans une classe qui accède aux membres privés et qui est appelée à partir d'un point différent dans le code!)
mmmmmmmm
9
@Poma C'est une raison étrange d'utiliser l'inline. Je doute fortement que cela se révèle efficace.
Chris Shouts
56
Les arguments selon lesquels le compilateur connaît le mieux sont tout simplement faux. Il n'inline aucune méthode supérieure à 32 octets IL, point. C'est horrible pour les projets (comme le mien, et aussi ceux d'Egor ci-dessus) qui ont des hotspots identifiés par le profileur sur lesquels nous ne pouvons absolument rien faire. Rien, c'est-à-dire, sauf pour couper et coller du code et donc manuellement en ligne. En un mot, c'est une situation terrible quand vous conduisez vraiment la performance.
lumineux
6
L'incrustation est plus subtile qu'il n'y paraît. La mémoire a des caches qui sont rapides d'accès, et la partie actuelle du code est stockée dans ces caches, tout comme les variables. Le chargement de la prochaine ligne d'instructions de cache est un échec de cache et coûte peut-être 10 fois ou plus ce qu'une instruction unique fait. Finalement, il doit se charger dans le cache L2, ce qui est encore plus cher. Ainsi, le code gonflé peut entraîner plus de cache manquant, où une fonction intégrée qui reste résidente dans les mêmes lignes de cache L1 tout le temps pourrait entraîner moins de ratés de cache et un code potentiellement plus rapide. Avec .Net, c'est encore plus complexe.
56

Mise à jour: Par la réponse de konrad.kruczynski , ce qui suit est vrai pour les versions de .NET jusques et y compris 4,0.

Vous pouvez utiliser la classe MethodImplAttribute pour empêcher une méthode d'être en ligne ...

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

... mais il n'y a aucun moyen de faire le contraire et de le forcer à être aligné.

BACON
la source
2
Intéressant de savoir cela, mais pourquoi diable empêcher une méthode d'être intégrée? J'ai passé un peu de temps à regarder le moniteur, mais je ne peux pas inventer une raison pour laquelle l'inline pourrait faire du mal.
Camilo Martin
3
Si vous recevez une pile d'appels (c'est-à-dire NullReferenceException) et évidemment, il n'y a aucun moyen que la méthode au-dessus de la pile la lance. Bien sûr, l'un de ses appels peut avoir. Mais lequel?
dzendras
19
GetExecutingAssemblyet GetCallingAssemblypeut donner des résultats différents selon que la méthode est en ligne ou non. Forcer une méthode à ne pas être alignée élimine toute incertitude.
stusmith
1
@Downvoter: si vous n'êtes pas d'accord avec ce que j'ai écrit, vous devriez poster un commentaire expliquant pourquoi. Non seulement c'est une courtoisie courante, mais vous n'accomplissez pas grand-chose en écrasant le total des votes d'une réponse de 20+.
BACON
2
J'aimerais pouvoir +2 parce que ton nom mérite à lui seul +1: D.
retrodrone
33

Vous mélangez deux concepts distincts. L'insertion de fonction est une optimisation du compilateur qui n'a aucun impact sur la sémantique. Une fonction se comporte de la même manière, qu'elle soit intégrée ou non.

En revanche, les fonctions lambda sont purement un concept sémantique. Il n'y a aucune exigence sur la façon dont ils doivent être implémentés ou exécutés, tant qu'ils suivent le comportement défini dans la spécification de langue. Ils peuvent être insérés si le compilateur JIT le souhaite, ou non si ce n'est pas le cas.

Il n'y a pas de mot clé en ligne en C #, car c'est une optimisation qui peut généralement être laissée au compilateur, en particulier dans les langages JIT. Le compilateur JIT a accès à des statistiques d'exécution qui lui permettent de décider quoi incorporer beaucoup plus efficacement que lors de l'écriture du code. Une fonction sera insérée si le compilateur décide de le faire, et vous ne pouvez rien y faire de toute façon. :)

jalf
la source
20
"Une fonction se comporte de la même manière, qu'elle soit intégrée ou non." Il existe de rares cas où cela n'est pas vrai: à savoir les fonctions de journalisation qui souhaitent connaître la trace de la pile. Mais je ne veux pas trop miner votre affirmation de base: c'est généralement vrai.
Joel Coehoorn
"et il n'y a rien que vous puissiez faire à ce sujet de toute façon." - pas vrai. Même si nous ne comptons pas «suggestion forte» pour le compilateur comme faisant quoi que ce soit, vous pouvez empêcher les fonctions d'être alignées.
BartoszKP
21

Voulez-vous dire les fonctions en ligne au sens C ++? Dans lequel le contenu d'une fonction normale est automatiquement copié en ligne dans le site d'appel? L'effet final étant qu'aucun appel de fonction ne se produit réellement lors de l'appel d'une fonction.

Exemple:

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

Si c'est le cas, non, il n'y a pas d'équivalent C # à cela.

Ou voulez-vous dire des fonctions qui sont déclarées dans une autre fonction? Si c'est le cas, alors oui, C # le prend en charge via des méthodes anonymes ou des expressions lambda.

Exemple:

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

Cody a raison, mais je veux donner un exemple de ce qu'est une fonction en ligne.

Disons que vous avez ce code:

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' optimiseur du compilateur Just-In-Time pourrait choisir de modifier le code pour éviter de placer à plusieurs reprises un appel à OutputItem () sur la pile, de sorte que ce serait comme si vous aviez écrit le code comme ceci à la place:

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

Dans ce cas, nous dirions que la fonction OutputItem () a été insérée. Notez qu'il peut le faire même si OutputItem () est également appelé depuis d'autres endroits.

Modifié pour montrer un scénario plus susceptible d'être intégré.

Joel Coehoorn
la source
9
Juste pour clarifier cependant; c'est le JIT qui fait l'inline; pas le compilateur C #.
Marc Gravell
Notez également qu'à l'origine au moins, le JIT «préférerait» les méthodes statiques intégrées, même au-delà des limites de l'assemblage. Ainsi, une technique d'optimisation à l'ancienne école consistait à marquer vos méthodes comme statiques. Cela a depuis été mal vu par la communauté, mais à ce jour, j'opterai toujours pour des méthodes en ligne qui sont de nature interne, non polymorphe et généralement moins coûteuses à exécuter que le remplissage + déversement de pile associé.
Shaun Wilson
7

Oui Exactement, la seule distinction est le fait qu'il renvoie une valeur.

Simplification (sans utiliser d'expressions):

List<T>.ForEach Prend une action, il ne s'attend pas à un résultat de retour.

Donc, un Action<T>délégué suffirait .. dire:

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

revient à dire:

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

la différence est que le type de param et la decleration de délégué sont inférés par l'utilisation et les accolades ne sont pas requises sur une méthode en ligne simple.

Tandis que

List<T>.Where Prend une fonction, attend un résultat.

On Function<T, bool>attendrait donc un :

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

ce qui équivaut à:

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

Vous pouvez également déclarer ces méthodes en ligne et les affecter aux variables IE:

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

myAction();

ou

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

string myString = myFunction(someObject);

J'espère que ça aide.

Quintin Robinson
la source
2

Il y a des occasions où je souhaite forcer le code à être aligné.

Par exemple, si j'ai une routine complexe où il y a un grand nombre de décisions prises dans un bloc hautement itératif et ces décisions entraînent des actions similaires mais légèrement différentes à mener. Considérons, par exemple, un comparateur de tri complexe (non piloté par une base de données) où l'algorithme de tri trie les éléments selon un certain nombre de critères différents, comme on pourrait le faire s'ils triaient des mots selon des critères grammaticaux et sémantiques pour un langage rapide système de reconnaissance. J'aurais tendance à écrire des fonctions d'aide pour gérer ces actions afin de maintenir la lisibilité et la modularité du code source.

Je sais que ces fonctions d'aide devraient être intégrées car c'est ainsi que le code serait écrit s'il n'avait jamais été compris par un humain. Je voudrais certainement m'assurer dans ce cas qu'il n'y avait pas de fonction appelant les frais généraux.

développeur
la source
2

L'énoncé "il vaut mieux laisser ces choses tranquilles et laisser le compilateur faire le travail .." (Cody Brocious) est complètement délabré. Je programme du code de jeu hautes performances depuis 20 ans, et je n'ai pas encore trouvé de compilateur suffisamment «intelligent» pour savoir quel code doit être intégré (fonctions) ou non. Il serait utile d'avoir une instruction "inline" en c #, la vérité est que le compilateur n'a tout simplement pas toutes les informations dont il a besoin pour déterminer quelle fonction doit toujours être en ligne ou non sans l'indication "en ligne". Bien sûr, si la fonction est petite (accesseur), elle peut être automatiquement intégrée, mais que se passe-t-il s'il s'agit de quelques lignes de code? Nonesense, le compilateur n'a aucun moyen de savoir, vous ne pouvez pas laisser cela au compilateur pour un code optimisé (au-delà des algorithmes).

gvick2002
la source
3
"Nonesense, le compilateur n'a aucun moyen de savoir" Le JITer fait le profilage en temps réel des appels de fonction ..
Brian Gordon
3
Le .NET JIT est connu pour optimiser au moment de l'exécution mieux que ce qui est possible avec une analyse statique en 2 passes au moment de la compilation. La partie difficile à comprendre pour les codeurs de la «vieille école» est que le JIT, pas le compilateur, est responsable de la génération de code natif. Le JIT, pas le compilateur, est responsable des méthodes en ligne.
Shaun Wilson
1
Il est possible pour moi de compiler le code dans lequel vous appelez, sans mon code source, et le JIT peut choisir d'inclure l'appel. Normalement, je serais d'accord, mais je dirais qu'en tant qu'humain, vous ne pouvez plus dépasser les outils.
Shaun Wilson
0

Non, il n'y a pas une telle construction en C #, mais le compilateur .NET JIT pourrait décider de faire des appels de fonction en ligne à l'heure JIT. Mais je ne sais pas vraiment s'il fait vraiment de telles optimisations.
(Je pense que ça devrait :-))

mmmmmmmm
la source
0

Dans le cas où vos assemblys seront ngen-ed, vous voudrez peut-être jeter un œil à TargetedPatchingOptOut. Cela aidera ngen à décider si les méthodes doivent être intégrées. Référence MSDN

Cependant, ce n'est qu'une indication déclarative à optimiser, pas une commande impérative.

Tim Lovell-Smith
la source
Et le JIT saura de toute façon beaucoup mieux s'il doit être en ligne ou non. Évidemment, cela ne vous aidera pas si le JIT n'optimise pas le site d'appel, mais alors pourquoi diable devrais-je m'inquiéter de la surcharge minimale d'une fonction qui n'est appelée que quelques fois?
Voo
-7

Les expressions lambda sont des fonctions en ligne! Je pense que C # n'a pas d'attribut supplémentaire comme inline ou quelque chose comme ça!

cordellcp3
la source
9
Je ne vous ai pas déçu, mais veuillez lire les questions et assurez-vous de bien les comprendre avant de poster une réponse aléatoire. en.wikipedia.org/wiki/Inline_function
Camilo Martin
1
Cette réponse n'est pas aussi mauvaise qu'elle a été formulée - l'OP n'est pas clair à propos de 'inline de fonction' vs 'fonctions lambda', et malheureusement MSDN fait référence à lambdas comme "déclarations en ligne" ou "code en ligne" . Cependant, selon la réponse de Konrad, il existe un attribut à suggérer au compilateur concernant l'inclusion d'une méthode.
StuartLC
-7

C # ne prend pas en charge les méthodes (ou fonctions) en ligne comme le font les langages dynamiques comme python. Cependant, les méthodes anonymes et les lambdas peuvent être utilisés à des fins similaires, y compris lorsque vous devez accéder à une variable dans la méthode conteneur comme dans l'exemple ci-dessous.

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

    Action inline = () => a++;
    inline();
    //here a = 2
}
Yordan Pavlov
la source
10
Qu'est-ce que cela a à voir avec les fonctions en ligne? c'est la méthode anonyme.
Tomer W