Função inline v. Macro em C – Qual é a sobrecarga (memory / velocidade)?

Eu pesquisei o Stack Overflow para os prós / contras de macros semelhantes a funções e funções inline.

Eu encontrei a seguinte discussão: Prós e contras de diferentes funções macro / methods inline em C

… mas não respondeu a minha principal questão de gravação.

Ou seja, qual é a sobrecarga em c de usar uma function de macro (com variables, possivelmente outras chamadas de function) v. Uma function inline, em termos de uso de memory e velocidade de execução?

Há alguma diferença dependente do compilador na sobrecarga? Eu tenho icc e gcc à minha disposição.

Meu trecho de código que estou modularizando é:

double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3); double RepulsiveTerm = AttractiveTerm * AttractiveTerm; EnergyContribution += 4 * Epsilon * (RepulsiveTerm - AttractiveTerm); 

Meu motivo para transformá-lo em uma function inline / macro é para que eu possa soltá-lo em um arquivo AC e, em seguida, compilar condicionalmente outras funções / macros semelhantes, mas ligeiramente diferentes.

por exemplo:

 double AttractiveTerm = pow(SigmaSquared/RadialDistanceSquared,3); double RepulsiveTerm = pow(SigmaSquared/RadialDistanceSquared,9); EnergyContribution += 4 * Epsilon * (RepulsiveTerm - AttractiveTerm); 

(note a diferença na segunda linha …)

Essa function é central para o meu código e é chamada milhares de vezes por etapa no meu programa, e meu programa executa milhões de etapas. Assim, eu quero ter o mínimo de sobrecarga possível, por isso estou perdendo tempo me preocupando com a sobrecarga de inline v. Transformando o código em uma macro.

Baseado na discussão anterior eu já percebi outros prós / contras (tipo independência e erros resultantes disso) de macros … mas o que eu quero saber mais, e não sei atualmente é o DESEMPENHO.

Eu sei que alguns de vocês, veteranos do C, terão uma ótima visão para mim !!

Chamar uma function in-line pode ou não gerar uma chamada de function, o que normalmente resulta em uma pequena quantidade de sobrecarga. As situações exatas sob as quais uma function inline realmente é inline variam dependendo do compilador; a maioria faz um esforço de boa fé para embutir pequenas funções (pelo menos quando a otimização é ativada), mas não há exigência de que elas façam isso (C99, §6.7.4):

Tornar uma function uma function inline sugere que as chamadas para a function sejam o mais rápidas possível. A extensão em que tais sugestões são eficazes é definida pela implementação.

É menos provável que uma macro incorra em tal sobrecarga (embora, novamente, haja pouco para impedir que um compilador faça alguma coisa; o padrão não define a que programas de código de máquina devem se expandir, apenas o comportamento observável de um programa compilado).

Use o que for mais limpo. Perfil. Se isso importa, faça algo diferente.

Além disso, o que fizzer disse; as chamadas para pow (e divisão) são normalmente mais caras do que a sobrecarga de chamada de function. Minimizar esses é um bom começo:

 double ratio = SigmaSquared/RadialDistanceSquared; double AttractiveTerm = ratio*ratio*ratio; EnergyContribution += 4 * Epsilon * AttractiveTerm * (AttractiveTerm - 1.0); 

A EnergyContribution é composta apenas por termos que se parecem com isso? Se assim for, puxe o 4 * Epsilon para fora e salve duas multiplicações por iteração:

 double ratio = SigmaSquared/RadialDistanceSquared; double AttractiveTerm = ratio*ratio*ratio; EnergyContribution += AttractiveTerm * (AttractiveTerm - 1.0); // later, once you've done all of those terms... EnergyContribution *= 4 * Epsilon; 

Uma macro não é realmente uma function. tudo o que você define como uma macro fica literalmente postado em seu código, antes que o compilador chegue a vê-lo, pelo pré-processador. O pré-processador é apenas uma ferramenta de engenheiros de software que permite várias abstrações para melhor estruturar seu código.

Uma function inline ou o compilador conhece e pode tomar decisões sobre o que fazer com ela. Uma palavra-chave inline é apenas uma sugestão e o compilador pode superá-la. É este excesso que na maioria dos casos resultaria em um código melhor.

Outro efeito colateral do compilador de estar ciente das funções é que você poderia potencialmente forçar o compilador a tomar certas decisões – por exemplo, desabilitando inlining do seu código, o que poderia permitir que você melhor debugging ou perfil de seu código. Provavelmente, há muitos outros casos de uso em que funções inline ativam vs. macros.

As macros são extremamente poderosas, e para fazer isso eu gostaria de citar o google test e google mock. Existem muitas razões para usar macros: D.

Operações matemáticas simples que são encadeadas usando funções são freqüentemente embutidas pelo compilador, especialmente se a function é chamada apenas uma vez na etapa de tradução. Então, eu não ficaria surpreso se o compilador toma decisões inline para você, independentemente do clima, a palavra-chave é fornecida ou não.

No entanto, se o compilador não puder, você pode manualmente simplificar segmentos do seu código. Se você planificar, talvez as macros sirvam como uma boa abstração, afinal elas apresentam uma semântica similar a uma function “real”.

O Crux

Então, você quer que o compilador esteja ciente de certos limites lógicos para que possa produzir um código físico melhor, ou você quer forçar as decisões no compilador achatando-o manualmente ou usando macros. A indústria se inclina para o primeiro.

Eu gostaria de usar macros neste caso, só porque é rápido e sujo, sem ter que aprender muito mais. No entanto, como macros são uma abstração de engenharia de software, e porque você está preocupado com o código que o compilador gera, se o problema se tornar um pouco mais avançado eu usaria modelos C ++, pois eles foram projetados para as preocupações que você está pensando.

São as chamadas para pow () que você quer eliminar. Essa function usa expoentes gerais de ponto flutuante e é ineficiente para elevar para expoentes integrais. Substituindo essas chamadas por, por exemplo

 inline double cube(double x) { return x * x * x; } 

é a única coisa que fará uma diferença significativa no seu desempenho aqui.

Por favor, reveja o padrão de codificação CERT Secure falando sobre macros e funções inline em termos de segurança e criação de bugs, eu não encorajo o uso de macros parecidas com funções, porque: – Menos Profiling – Menos Rastreável – Mais Difícil de Depurar – Poderia Levar a Erros Severos

A melhor maneira de responder à sua pergunta é comparar as duas abordagens para ver qual delas é realmente mais rápida em seu aplicativo, usando seus dados de teste. As previsões sobre desempenho são notoriamente não confiáveis, exceto nos níveis mais grosseiros.

Dito isso, eu esperaria que não houvesse diferença significativa entre uma macro e uma chamada de function verdadeiramente inline. Em ambos os casos, você deve acabar com o mesmo código de assembly sob o capô.

Macros, incluindo macros parecidas com funções, são simples substituições de texto e, como tal, podem incomodá-lo se você não for muito cuidadoso com seus parâmetros. Por exemplo, a macro SQUARE sempre tão popular:

 #define SQUARE(x) ((x)*(x)) 

pode ser um desastre esperando para acontecer se você chamá-lo como SQUARE(i++) . Além disso, macros semelhantes a funções não têm conceito de escopo e não suportam variables ​​locais; o hack mais popular é algo como

 #define MACRO(S,R,E,C) \ do \ { \ double AttractiveTerm = pow((S)/(R),3); \ double RepulsiveTerm = AttractiveTerm * AttractiveTerm; \ (C) = 4 * (E) * (RepulsiveTerm - AttractiveTerm); \ } while(0) 

o que, é claro, dificulta a atribuição de um resultado como x = MACRO(a,b); .

A melhor aposta do ponto de vista de correção e manutenção é torná-la uma function e especificar inline . As macros não são funções e não devem ser confundidas com elas.

Uma vez feito isso, meça o desempenho e encontre onde o gargalo é antes de hackeá-lo (a chamada para o pow certamente seria um candidato para simplificar).

Se você pausar isso aleatoriamente , o que você provavelmente verá é que 100% (menos epsilon) do tempo está dentro da function pow , então, como ele chegou lá, basicamente não faz diferença.

Assumindo que você descobre que, a primeira coisa a fazer é se livrar das chamadas para pow que você encontrou na pilha. (Em geral, o que ele faz é pegar o log do primeiro argumento, multiplicá-lo pelo segundo argumento e exp desse, ou algo que faz a mesma coisa. O log e o exp podem muito bem ser feitos por algum tipo de série envolvendo Muita aritmética, é claro, para casos especiais, mas ainda vai demorar mais tempo do que você. Isso só poderia dar a você uma aceleração de ordem de grandeza.

Então faça a pausa aleatória novamente. Agora você vai ver outra coisa levando muito tempo. Eu não posso adivinhar o que será, e ninguém pode, mas provavelmente você pode reduzir isso também. Continue fazendo isso até você não poder mais.

Isso pode acontecer ao longo do caminho que você escolher para usar uma macro, e pode ser um pouco mais rápido do que uma function in-line. Isso é para você julgar quando chegar lá.

como outros disseram, depende principalmente do compilador.

Eu aposto que “pow” custa mais do que qualquer inline ou macro vai te salvar 🙂

Eu acho que é mais limpo se é uma function inline, em vez de uma macro.

cache e pipelining são realmente onde você vai obter bons ganhos se você estiver executando isso em um processador moderno. ie. remover instruções de ramificação como ‘if’ fazem diferenças enormes (pode ser feito por um número de truques)

Pelo que entendi de alguns caras que escrevem compiladores, uma vez que você chama uma function de dentro, não é muito provável que seu código seja embutido de qualquer maneira. Mas é por isso que você não deve usar uma macro. As macros removem informações e deixam o compilador com muito menos opções para otimizar. Com compiladores multi-pass e otimizações de programas inteiros, eles saberão que o inlining do seu código causará uma previsão de ramificação com falha ou um erro de cache ou outras forças da magia negra que as CPUs modernas usam para ir mais rápido. Eu acho que todo mundo está certo em apontar que o código acima não é ideal de qualquer maneira, então é aí que o foco deveria estar.