Funções embutidas em c #?

276

Como você faz "funções embutidas" em c #? Acho que não entendo o conceito. Eles são como métodos anônimos? Como funções lambda?

Nota : As respostas tratam quase inteiramente da capacidade de incorporar funções , como "uma otimização manual ou do compilador que substitui um site de chamada de função pelo corpo do receptor". Se você estiver interessado em funções anônimas (também conhecidas como lambda) , consulte a resposta do @ jalf ou Em que é esse 'Lambda' que todo mundo continua falando? .

Dinah
fonte
11
Finalmente é possível - veja minha resposta.
Konrad.kruczynski
1
Para o mais próximo do .NET 4.5, consulte a pergunta Como definir variável como função lambda . Na verdade, NÃO está compilando como embutido, mas é uma declaração curta de função, como declaração embutida. Depende do que você está tentando alcançar usando inline.
AppFzx
Para as pessoas curiosas, confira esta extensão VS .
TripleAccretion 04/01

Respostas:

384

Finalmente, no .NET 4.5, o CLR permite sugerir / sugerir 1 método embutido usando MethodImplOptions.AggressiveInliningvalue. Também está disponível no porta-malas do Mono (confirmado hoje).

// 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 "força" era usada aqui. Como houve alguns votos negativos, tentarei esclarecer o termo. Como nos comentários e na documentação, The method should be inlined if possible.Especialmente considerando o Mono (que está aberto), existem algumas limitações técnicas mono-específicas, considerando inlining ou mais gerais (como funções virtuais). No geral, sim, essa é uma dica para o compilador, mas acho que foi o que foi solicitado.

konrad.kruczynski
fonte
17
+1 - Atualizou sua resposta para ser mais específico sobre os requisitos de versão da estrutura.
precisa saber é o seguinte
4
Provavelmente ainda não é uma força em linha, mas substituir as heurísticas do JITters certamente é suficiente na maioria das situações.
CodesInChaos
7
Uma abordagem diferente que pode funcionar com toda a versão .NET é dividir um método um pouco grande demais em dois métodos, um que chama o outro, nenhum dos quais excede 32 bytes de IL. O efeito líquido é como se o original estivesse embutido.
Rick Sladkey
4
Não está forçando o "Inlining", é apenas uma tentativa de conversar com o JIT e dizer que o programador realmente deseja usar o Inlining aqui, mas o JIT tem a palavra final. Portanto, o MSDN: o método deve ser incorporado, se possível.
Orel Eraki
11
Por comparação, as sugestões inline do C ++, mesmo as específicas do compilador, também não forçam uma inline: nem todas as funções podem ser incorporadas (fundamentalmente coisas como funções recursivas são difíceis, mas também existem outros casos). Então, sim, essa força "não exatamente" é típica.
Eamon Nerbonne
87

Os métodos embutidos são simplesmente uma otimização do compilador, onde o código de uma função é rolado no chamador.

Não há mecanismo para fazer isso em C #, e eles devem ser usados ​​com moderação nos idiomas em que são suportados - se você não sabe por que eles devem ser usados ​​em algum lugar, eles não deveriam.

Editar: para esclarecer, há duas razões principais pelas quais elas precisam ser usadas com moderação:

  1. É fácil criar binários enormes usando inline nos casos em que não é necessário
  2. O compilador tende a saber melhor do que você quando algo, do ponto de vista de desempenho, deve ser incorporado

É melhor deixar as coisas em paz e deixar o compilador fazer o seu trabalho; em seguida, faça um perfil e descubra se a linha é a melhor solução para você. Obviamente, algumas coisas fazem sentido para serem incorporadas (principalmente operadores matemáticos), mas deixar o compilador lidar com isso é normalmente a melhor prática.

Cody Brocious
fonte
35
Normalmente, eu acho que o compilador lida com inlining. Mas há situações em que gosto de substituir a decisão do compilador e incorporar ou não um método.
mmmmmmmm
7
@ Joel Coehoorn: Isso seria uma prática ruim, porque quebraria a modularização e a proteção de acesso. (Pense em um método inline em uma classe que acessa os membros privados e é chamado de ponto diferente no código!)
mmmmmmmm
9
@ Poma Esse é um motivo estranho para usar inlining. Duvido muito que isso provasse ser eficaz.
Chris Gritos
56
Os argumentos sobre o melhor conhecimento do compilador estão errados. Ele não inline nenhum método maior que 32 bytes IL, ponto final. Isso é horrível para projetos (como o meu e também o do Egor acima) que possuem pontos de acesso identificados pelo criador de perfil sobre os quais não podemos fazer absolutamente nada. Nada, exceto cortar e colar o código e incorporar manualmente. Em suma, é uma situação terrível quando você realmente está dirigindo o desempenho.
brilhante
6
Inlining é mais sutil do que parece. A memória possui caches de acesso rápido e a parte atual do código é armazenada nesses caches exatamente como as variáveis. Carregar a próxima linha de instruções de cache é um erro de cache e custa talvez 10 ou mais vezes o que uma única instrução faz. Eventualmente, ele precisa carregar no cache L2, o que é ainda mais caro. Assim, o código inchado pode causar mais perda de cache, onde uma função embutida que permanece residente nas mesmas linhas de cache L1 o tempo todo pode resultar em menos falhas de cache e código potencialmente mais rápido. Com .Net é ainda mais complexo.
56

Atualização: de acordo com a resposta de konrad.kruczynski , o seguinte é verdadeiro para versões do .NET até 4.0, inclusive.

Você pode usar a classe MethodImplAttribute para impedir que um método seja incorporado.

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

... mas não há como fazer o oposto e forçar a inclusão.

BACON
fonte
2
Interessante saber disso, mas por que diabos impedir um método a ser incorporado? Passei algum tempo olhando para o monitor, mas não consigo inventar uma razão pela qual inlining poderia causar algum dano.
Camilo Martin
3
Se você receber uma pilha de chamadas (ou seja, NullReferenceException) e, obviamente, não há como o método no topo da pilha a lançar. Claro, uma de suas chamadas pode ter. Mas qual deles?
dzendras
19
GetExecutingAssemblye GetCallingAssemblypode fornecer resultados diferentes, dependendo se o método está embutido. Forçar um método a não ser alinhado elimina qualquer incerteza.
stusmith
1
@ Downvoter: se você não concorda com o que eu escrevi, você deve postar um comentário explicando o porquê. Não é apenas uma cortesia comum, você também não realiza muita coisa ao somar o total de votos de uma resposta de mais de 20.
BACON
2
Gostaria de poder +2 porque apenas o seu nome merece +1: D.
retrodrone #
33

Você está misturando dois conceitos separados. A função inlining é uma otimização de compilador que não tem impacto na semântica. Uma função se comporta da mesma forma, seja embutida ou não.

Por outro lado, funções lambda são puramente um conceito semântico. Não há requisitos sobre como eles devem ser implementados ou executados, desde que sigam o comportamento estabelecido nas especificações de idioma. Eles podem ser incorporados se o compilador JIT desejar ou não.

Não há palavra-chave embutida em C #, porque é uma otimização que geralmente pode ser deixada para o compilador, especialmente nas linguagens JIT. O compilador JIT tem acesso às estatísticas de tempo de execução, o que permite decidir o que incorporar com muito mais eficiência do que você pode escrever ao escrever o código. Uma função será incorporada se o compilador decidir, e não há nada que você possa fazer sobre isso de qualquer maneira. :)

jalf
fonte
20
"Uma função se comporta da mesma forma, seja embutida ou não." Existem alguns casos raros em que isso não é verdade: a saber, funções de registro que desejam saber sobre o rastreamento de pilha. Mas não quero minar muito sua declaração básica: geralmente é verdade.
Joel Coehoorn
"e não há nada que você possa fazer sobre isso de qualquer maneira." - não é verdade. Mesmo que não contemos "sugestões fortes" ao compilador como fazendo algo, você pode impedir que funções sejam incorporadas.
precisa saber é o seguinte
21

Você quer dizer funções embutidas no sentido de C ++? Em que o conteúdo de uma função normal é automaticamente copiado em linha para o local da chamada? O efeito final é que nenhuma chamada de função realmente acontece ao chamar uma função.

Exemplo:

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

Se sim, então não, não há C # equivalente a isso.

Ou você quer dizer funções declaradas em outra função? Nesse caso, sim, o C # suporta isso por métodos anônimos ou expressões lambda.

Exemplo:

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

Cody está certo, mas quero fornecer um exemplo do que é uma função embutida.

Digamos que você tenha 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;
}

O otimizador Just-In-Time do compilador pode optar por alterar o código para evitar repetidamente fazer uma chamada para OutputItem () na pilha, de modo que seria como se você tivesse escrito o código dessa maneira:

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

Nesse caso, diríamos que a função OutputItem () estava embutida. Observe que isso pode ser feito mesmo que o OutputItem () seja chamado de outros lugares também.

Editado para mostrar um cenário com maior probabilidade de ser incorporado.

Joel Coehoorn
fonte
9
Apenas para esclarecer; é o JIT que faz o inlining; não o compilador C #.
Marc Gravell
Observe também que, pelo menos originalmente, o JIT 'preferiria' incorporar métodos estáticos, mesmo através dos limites de montagem. Assim, uma técnica antiga de otimização era marcar seus métodos como estáticos. Isso já foi desaprovado pela comunidade, mas até hoje continuarei optando por métodos em linha que são internos, de natureza não polimórfica e geralmente custam menos para executar do que o preenchimento de pilha associado.
Shaun Wilson
7

Sim Exatamente, a única distinção é o fato de retornar um valor.

Simplificação (sem usar expressões):

List<T>.ForEach Executa uma ação, não espera um resultado de retorno.

Portanto, um Action<T>delegado seria suficiente .. diga:

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

é o mesmo que dizer:

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

a diferença é que o tipo de parâmetro e a decleração de delegação são inferidos pelo uso e as chaves não são necessárias em um método embutido simples.

Enquanto que

List<T>.Where Tem uma função, esperando um resultado.

Portanto, Function<T, bool>seria de se esperar:

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

que é o mesmo que:

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

Você também pode declarar esses métodos embutidos e atribuí-los às variáveis ​​IE:

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

myAction();

ou

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

string myString = myFunction(someObject);

Eu espero que isso ajude.

Quintin Robinson
fonte
2

Há ocasiões em que desejo forçar o código a ser alinhado.

Por exemplo, se eu tenho uma rotina complexa em que há um grande número de decisões tomadas dentro de um bloco altamente iterativo e essas decisões resultam em ações semelhantes, mas ligeiramente diferentes, a serem executadas. Considere, por exemplo, um comparador de classificação complexo (não controlado pelo DB), em que o algoritmo de classificação classifica os elementos de acordo com vários critérios diferentes, como se poderia classificar palavras de acordo com critérios gramaticais e semânticos para um idioma rápido sistema de reconhecimento. Eu tenderia a escrever funções auxiliares para lidar com essas ações, a fim de manter a legibilidade e a modularidade do código-fonte.

Eu sei que essas funções auxiliares devem estar alinhadas, porque é dessa maneira que o código seria escrito se nunca tivesse que ser entendido por um humano. Eu certamente gostaria de garantir, neste caso, que não houvesse nenhuma função chamando sobrecarga.

desenvolvedor
fonte
2

A afirmação "é melhor deixar essas coisas em paz e deixar que o compilador faça o trabalho .." (Cody Brocious) é uma loucura completa. Estou programando código de jogo de alto desempenho há 20 anos e ainda preciso encontrar um compilador que seja 'inteligente o suficiente' para saber qual código deve ser incorporado (funções) ou não. Seria útil ter uma declaração "embutida" em c #, a verdade é que o compilador simplesmente não possui todas as informações necessárias para determinar qual função deve estar sempre embutida ou não sem a dica "embutida". Certamente, se a função é pequena (acessador), ela pode ser automaticamente incorporada, mas e se houver algumas linhas de código? Sem sentido, o compilador não tem como saber, você não pode deixar isso para o compilador por código otimizado (além dos algoritmos).

gvick2002
fonte
3
"Bobagem, o compilador não tem como saber" O JITer faz o perfil em tempo real das chamadas de função.
Brian Gordon
3
Sabe-se que o .NET JIT otimiza em tempo de execução melhor do que é possível com uma análise estática de 2 passagens em tempo de compilação. A parte difícil para os codificadores da "velha escola" compreenderem é que o JIT, não o compilador, é responsável pela geração de código nativo. O JIT, não o compilador, é responsável pelos métodos em linha.
Shaun Wilson
1
É possível compilar o código para o qual você chama, sem o meu código-fonte, e o JIT pode optar por incorporar a chamada. Normalmente eu concordaria, mas diria que como humano você não é mais capaz de superar as ferramentas.
Shaun Wilson
0

Não, não existe essa construção em C #, mas o compilador .NET JIT pode decidir fazer chamadas de função em linha no tempo JIT. Mas na verdade não sei se realmente está fazendo essas otimizações.
(Eu acho que deveria :-))

mmmmmmmm
fonte
0

Caso suas montagens sejam geradas, você pode dar uma olhada no TargetedPatchingOptOut. Isso ajudará a ngen a decidir se métodos embutidos.Referência MSDN

Ainda é apenas uma dica declarativa para otimizar, não um comando imperativo.

Tim Lovell-Smith
fonte
E o JIT saberá muito melhor se está alinhado ou não. Obviamente, não o ajudará se o JIT não otimizar o local da chamada, mas por que diabos devo me preocupar com a sobrecarga mínima de uma função chamada apenas algumas vezes?
Voo
-7

Expressões lambda são funções embutidas! Eu acho que o C # não tem um atributo extra como inline ou algo assim!

cordellcp3
fonte
9
Não diminuí a votação, mas leia as perguntas e entenda-as antes de postar uma resposta aleatória. en.wikipedia.org/wiki/Inline_function
Camilo Martin
1
Essa resposta não é tão ruim quanto parece - o OP não está claro sobre 'função inlining' vs 'funções lambda' e, infelizmente, o MSDN se refere a lambdas como "declarações inline" ou "código inline" . No entanto, de acordo com a resposta do Konrad, há um atributo para sugerir ao compilador a inclusão de um método.
StuartLC 23/02/15
-7

O C # não oferece suporte a métodos (ou funções) embutidos da mesma maneira que linguagens dinâmicas como o python. No entanto, métodos anônimos e lambdas podem ser usados ​​para fins semelhantes, inclusive quando você precisa acessar uma variável no método que contém, como no exemplo abaixo.

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

    Action inline = () => a++;
    inline();
    //here a = 2
}
Yordan Pavlov
fonte
10
O que isso tem a ver com funções embutidas? esse é o método anônimo.
amigos estão