Funciones en línea en C #?

276

¿Cómo se hacen las "funciones en línea" en C #? No creo entender el concepto. ¿Son como métodos anónimos? ¿Te gustan las funciones lambda?

Nota : Las respuestas tratan casi por completo de la capacidad de incorporar funciones en línea , es decir, "una optimización manual o de compilación que reemplaza un sitio de llamada de función con el cuerpo del destinatario". Si está interesado en funciones anónimas (también conocidas como lambda) , consulte la respuesta de @ jalf o ¿De qué se trata esta 'Lambda' de la que todos hablan? .

Dinah
fuente
11
Finalmente es posible, mira mi respuesta.
konrad.kruczynski
1
Para lo más cercano antes de .NET 4.5, vea la pregunta Cómo definir la variable como función lambda . En realidad NO se está compilando como en línea, pero es una declaración de función corta como la declaración en línea. Depende de lo que estés tratando de lograr usando Inline.
AppFzx
Para la gente curiosa, echa un vistazo a esta extensión VS .
TripleAccretion

Respuestas:

384

Finalmente en .NET 4.5, el CLR le permite a uno insinuar / sugerir 1 método en línea usando el MethodImplOptions.AggressiveInliningvalor. También está disponible en el maletero del Mono (comprometido hoy).

// 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 . Anteriormente "fuerza" se usaba aquí. Como hubo algunos votos negativos, intentaré aclarar el término. Al igual que en los comentarios y la documentación, The method should be inlined if possible.especialmente teniendo en cuenta Mono (que está abierto), existen algunas limitaciones técnicas monoespecíficas que consideran la inclusión en línea o una más general (como funciones virtuales). En general, sí, esto es una pista para el compilador, pero supongo que eso es lo que se pidió.

konrad.kruczynski
fuente
17
+1: actualizó su respuesta para ser más específico sobre los requisitos de la versión del marco.
M.Babcock
44
Probablemente todavía no sea una fuerza en línea, pero anular la heurística de JITters es ciertamente suficiente en la mayoría de las situaciones.
CodesInChaos
77
Un enfoque diferente que puede funcionar con todas las versiones .NET es dividir un método un poco demasiado grande en dos métodos, uno que llame al otro, ninguno de los cuales excede los 32 bytes de IL. El efecto neto es como si el original estuviera en línea.
Rick Sladkey
44
No está obligando a "Inlining", solo trata de hablar con el JIT y decirle que el programador realmente quiere usar Inlining aquí, pero el JIT tiene la última palabra. Por lo tanto, MSDN: el método debe estar en línea si es posible.
Orel Eraki
11
En comparación, las sugerencias en línea de C ++, incluso las específicas del compilador, tampoco fuerzan realmente una línea: no todas las funciones pueden estar en línea (fundamentalmente, cosas como las funciones recursivas son difíciles, pero también hay otros casos). Entonces sí, esta fuerza "no del todo" es típica.
Eamon Nerbonne
87

Los métodos en línea son simplemente una optimización del compilador donde el código de una función se incluye en la persona que llama.

No hay ningún mecanismo para hacer esto en C #, y se deben usar con moderación en los lenguajes donde son compatibles; si no sabe por qué deberían usarse en algún lugar, no deberían serlo.

Editar: para aclarar, hay dos razones principales por las que deben usarse con moderación:

  1. Es fácil hacer binarios masivos usando en línea en casos donde no es necesario
  2. El compilador tiende a saber mejor que usted cuando algo debería, desde el punto de vista del rendimiento, estar en línea

Es mejor dejar las cosas en paz y dejar que el compilador haga su trabajo, luego perfile y descubra si en línea es la mejor solución para usted. Por supuesto, algunas cosas simplemente tienen sentido estar en línea (operadores matemáticos en particular), pero dejar que el compilador lo maneje suele ser la mejor práctica.

Cody Brocious
fuente
35
Normalmente creo que está bien que el compilador maneje la inserción. Pero hay situaciones en las que me gusta anular la decisión del compilador y en línea o no en línea un método.
mmmmmmmm
77
@ Joel Coehoorn: Esta sería una mala práctica porque rompería la modularización y la protección de acceso. (¡Piense en un método en línea en una clase que accede a miembros privados y se llama desde un punto diferente en el código!)
mmmmmmmm
9
@Poma Esa es una razón extraña para usar inlining. Dudo mucho que resulte efectivo.
Chris grita el
56
Los argumentos sobre el compilador que mejor sabe son simplemente incorrectos. No incluye ningún método mayor de 32 bytes IL, punto. Esto es horrible para proyectos (como el mío y también el de Egor arriba) que tienen puntos críticos identificados por el generador de perfiles sobre los que no podemos hacer absolutamente nada. Nada, es decir, excepto cortar y pegar código y, por lo tanto, manualmente en línea. Esto es, en una palabra, un terrible estado de cosas cuando realmente manejas el rendimiento.
brillante
66
La alineación es más sutil de lo que parece. La memoria tiene cachés de acceso rápido, y la parte actual del código se almacena en esas cachés al igual que las variables. Cargar la siguiente línea de instrucciones de caché es una falta de caché, y cuesta tal vez 10 o más veces lo que hace una sola instrucción. Eventualmente tiene que cargarse en el caché L2, que es aún más costoso. Por lo tanto, el código hinchado puede causar que se pierda más caché, donde una función en línea que permanece residente en las mismas líneas de caché L1 todo el tiempo podría resultar en menos errores de caché y un código potencialmente más rápido. Con .Net es aún más complejo.
56

Actualización: Por la respuesta de konrad.kruczynski , lo siguiente es cierto para las versiones de .NET hasta e incluyendo 4,0.

Puede usar la clase MethodImplAttribute para evitar que un método se inserte ...

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

... pero no hay forma de hacer lo contrario y forzarlo a estar en línea.

TOCINO
fuente
2
Es interesante saber eso, pero ¿por qué diablos impiden que un método sea incorporado? Pasé algún tiempo mirando el monitor, pero no puedo inventar una razón por la cual la alineación podría causar algún daño.
Camilo Martin
3
Si recibe una pila de llamadas (es decir, NullReferenceException) y, obviamente, no hay forma de que el método en la parte superior de la pila la arroje. Por supuesto, una de sus llamadas puede tener. ¿Pero cual?
dzendras
19
GetExecutingAssemblyy GetCallingAssemblypuede dar resultados diferentes dependiendo de si el método está en línea. Forzar que un método no esté en línea elimina cualquier incertidumbre.
stusmith
1
@Downvoter: si no está de acuerdo con lo que he escrito, debe publicar un comentario explicando por qué. No solo es una cortesía común, sino que tampoco logras mucho al eliminar el total de votos de una respuesta de más de 20.
BACON
2
Ojalá pudiera +2 porque tu nombre solo merece +1: D.
retrodrone el
33

Estás mezclando dos conceptos separados. La función en línea es una optimización del compilador que no tiene impacto en la semántica. Una función se comporta igual si está en línea o no.

Por otro lado, las funciones lambda son puramente un concepto semántico. No hay ningún requisito sobre cómo deben implementarse o ejecutarse, siempre y cuando sigan el comportamiento establecido en la especificación del lenguaje. Pueden estar en línea si el compilador JIT lo desea, o no si no lo hace.

No hay una palabra clave en línea en C #, porque es una optimización que generalmente se puede dejar al compilador, especialmente en los lenguajes JIT. El compilador JIT tiene acceso a estadísticas de tiempo de ejecución que le permiten decidir qué incluir en línea de manera mucho más eficiente que cuando escribe el código. Una función estará en línea si el compilador lo decide, y no hay nada que pueda hacer al respecto de ninguna manera. :)

jalf
fuente
20
"Una función se comporta igual si está en línea o no". Hay algunos casos raros en los que esto no es cierto: a saber, las funciones de registro que desean saber sobre el seguimiento de la pila. Pero no quiero socavar demasiado su declaración base: generalmente es cierto.
Joel Coehoorn el
"y no hay nada que puedas hacer al respecto de ninguna manera", no es cierto. Incluso si no estamos contando "sugerencia fuerte" para el compilador como algo que puede hacer, puede evitar que las funciones estén en línea.
BartoszKP
21

¿Te refieres a las funciones en línea en el sentido de C ++? ¿En qué los contenidos de una función normal se copian automáticamente en línea en el sitio de llamadas? El efecto final es que no se produce ninguna llamada a la función cuando se llama a una función.

Ejemplo:

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

Si es así, entonces no, no hay C # equivalente a esto.

¿O quiere decir funciones que se declaran dentro de otra función? Si es así, sí, C # admite esto mediante métodos anónimos o expresiones lambda.

Ejemplo:

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

Cody tiene razón, pero quiero proporcionar un ejemplo de lo que es una función en línea.

Digamos que tienes este código:

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

El optimizador Just-In-Time del compilador podría elegir alterar el código para evitar hacer una llamada repetida a OutputItem () en la pila, de modo que sería como si hubiera escrito el código de esta manera:

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

En este caso, diríamos que la función OutputItem () estaba en línea. Tenga en cuenta que podría hacerlo incluso si se llama a OutputItem () desde otros lugares también.

Editado para mostrar un escenario con más probabilidades de estar en línea.

Joel Coehoorn
fuente
9
Sin embargo, solo para aclarar; es el JIT el que hace la alineación; no el compilador de C #.
Marc Gravell
También tenga en cuenta que, al menos originalmente, el JIT 'preferiría' utilizar métodos estáticos en línea, incluso a través de los límites de ensamblaje. Por lo tanto, una técnica de la vieja escuela para la optimización era marcar sus métodos como estáticos. Desde entonces, la comunidad no lo ha visto bien, pero hasta el día de hoy seguiré optando por métodos en línea que son de naturaleza interna, no polimórfica y, por lo general, cuestan menos que el relleno de pila + derrame asociado.
Shaun Wilson
7

Sí. Exactamente, la única distinción es el hecho de que devuelve un valor.

Simplificación (sin usar expresiones):

List<T>.ForEach Toma una acción, no espera un resultado de retorno.

Entonces un Action<T>delegado sería suficiente ... diga:

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

es lo mismo que decir:

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

la diferencia es que el tipo de parámetro y la declinación delegada se infieren por el uso y no se requieren llaves en un método simple en línea.

Donde como

List<T>.Where Toma una función, esperando un resultado.

Entonces Function<T, bool>se esperaría un:

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

que es lo mismo que:

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

También puede declarar estos métodos en línea y asignarlos a las variables IE:

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

myAction();

o

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

string myString = myFunction(someObject);

Espero que esto ayude.

Quintin Robinson
fuente
2

Hay ocasiones en las que deseo forzar el código para que esté alineado.

Por ejemplo, si tengo una rutina compleja donde hay una gran cantidad de decisiones tomadas dentro de un bloque altamente iterativo y esas decisiones resultan en acciones similares pero ligeramente diferentes que se llevarán a cabo. Considere, por ejemplo, un comparador de clasificación complejo (no basado en DB) donde el algoritmo de clasificación clasifica los elementos de acuerdo con una serie de criterios diferentes no relacionados, como uno podría hacer si clasificaran las palabras de acuerdo con criterios gramaticales y semánticos para un lenguaje rápido sistema de reconocimiento Tendería a escribir funciones auxiliares para manejar esas acciones con el fin de mantener la legibilidad y la modularidad del código fuente.

Sé que esas funciones auxiliares deberían estar alineadas porque esa es la forma en que se escribiría el código si un humano nunca tuviera que entenderlo. Ciertamente, me gustaría asegurarme en este caso de que no haya ninguna función llamando a la sobrecarga.

desarrollador
fuente
2

La afirmación "es mejor dejar estas cosas en paz y dejar que el compilador haga el trabajo ..." (Cody Brocious) es una completa tontería. He estado programando código de juego de alto rendimiento durante 20 años, y todavía tengo que encontrar un compilador que sea 'lo suficientemente inteligente' como para saber qué código debe estar integrado (funciones) o no. Sería útil tener una declaración "en línea" en C #, la verdad es que el compilador simplemente no tiene toda la información que necesita para determinar qué función debe estar siempre en línea o no sin la pista "en línea". Claro, si la función es pequeña (accesor), entonces podría estar en línea automáticamente, pero ¿qué pasa si se trata de unas pocas líneas de código? Nonesense, el compilador no tiene forma de saberlo, no puede dejar eso en manos del compilador para obtener un código optimizado (más allá de los algoritmos).

gvick2002
fuente
3
"Nonesense, el compilador no tiene forma de saberlo" El JITer hace perfiles en tiempo real de las llamadas a funciones ..
Brian Gordon
3
Se sabe que .NET JIT se optimiza en tiempo de ejecución mejor de lo que es posible con un análisis estático de 2 pasadas en tiempo de compilación. La parte difícil para los codificadores de la "vieja escuela" es comprender que el JIT, no el compilador, es responsable de la generación de código nativo. El JIT, no el compilador, es responsable de los métodos en línea.
Shaun Wilson
1
Es posible para mí compilar el código al que llama, sin mi código fuente, y el JIT puede optar por incluir la llamada. Normalmente estaría de acuerdo, pero diría que como humano ya no puedes superar las herramientas.
Shaun Wilson
0

No, no existe tal construcción en C #, pero el compilador .NET JIT podría decidir hacer llamadas de función en línea en tiempo JIT. Pero en realidad no sé si realmente está haciendo tales optimizaciones.
(Creo que debería :-))

mmmmmmmm
fuente
0

En caso de que sus ensamblajes sean ngen-ed, es posible que desee echar un vistazo a TargetedPatchingOptOut. Esto ayudará a ngen a decidir si utilizar métodos en línea. Referencia de MSDN

Sin embargo, todavía es solo una pista declarativa para optimizar, no un comando imperativo.

Tim Lovell-Smith
fuente
Y el JIT sabrá mucho mejor de todos modos si está en línea o no. Obviamente no lo ayudará si el JIT no optimiza el sitio de llamadas, pero ¿por qué demonios debería preocuparme por la sobrecarga mínima de una función que se llama solo unas pocas veces?
Voo
-7

¡Las expresiones lambda son funciones en línea! ¡Creo que C # no tiene un atributo adicional como en línea o algo así!

cordellcp3
fuente
9
No te voté en contra, pero por favor lee las preguntas y asegúrate de entenderlas antes de publicar una respuesta aleatoria. en.wikipedia.org/wiki/Inline_function
Camilo Martin
1
Esta respuesta no es tan mala como parece: el OP no está claro sobre 'función en línea' frente a 'funciones lambda', y desafortunadamente MSDN se refiere a lambdas como "declaraciones en línea" o "código en línea" . Sin embargo, según la respuesta de Konrad, hay un atributo que sugiere al compilador que incluya un método.
StuartLC
-7

C # no admite métodos en línea (o funciones) en la forma en que lo hacen los lenguajes dinámicos como python. Sin embargo, los métodos anónimos y las lambdas se pueden usar para fines similares, incluso cuando necesita acceder a una variable en el método que contiene, como en el ejemplo a continuación.

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

    Action inline = () => a++;
    inline();
    //here a = 2
}
Yordan Pavlov
fuente
10
¿Qué tiene esto que ver con las funciones en línea? Este es el método anónimo.
Tomer W