Pergunta

Eu sou desenvolvedor de C / C ++, e aqui estão algumas perguntas que sempre me perplexo.

  • Existe uma grande diferença entre o código "regular" e código embutido?
  • Qual é a principal diferença?
  • É código embutido simplesmente uma "forma" de macros?
  • Que tipo de troca deve ser feito ao escolher para inline seu código?

Graças

Foi útil?

Solução

  • Existe uma grande diferença entre o código "regular" e código embutido?

Sim e não. Não, porque uma função inline ou método tem exatamente as mesmas características de uma forma regular, mais um importante é que ambos são tipo seguro. E sim, porque o código assembly gerado pelo compilador será diferente; com uma função de regular, cada chamada será traduzido em várias etapas: empurrando parâmetros na pilha, tornando o salto para a função, estalando os parâmetros, etc, enquanto uma chamada para uma função inline será substituído pelo seu código real, como um macro.

  • É código embutido simplesmente uma "forma" de macros?

Não ! A macro é a substituição de texto simples, que pode levar a erros graves. Considere o seguinte código:

#define unsafe(i) ( (i) >= 0 ? (i) : -(i) )

[...]
unsafe(x++); // x is incremented twice!
unsafe(f()); // f() is called twice!
[...]

Usando uma função inline, você está certo de que os parâmetros serão avaliados antes que a função é realmente realizado. Eles também serão Tipo marcada, e, eventualmente, convertido para coincidir com os tipos de parâmetros formais.

  • Que tipo de troca deve ser feito ao escolher para inline seu código?

Normalmente, a execução do programa deve ser mais rápido ao usar funções inline, mas com um código binário maior. Para mais informações, você deve ler GoTW # 33 .

Outras dicas

Desempenho

Como foi sugerido nas respostas anteriores, o uso da palavra-chave inline pode tornar o código mais rápido, inlining chamadas de função, muitas vezes à custa do aumento dos executáveis. “Inlining chamadas de função” significa apenas substituindo a chamada para a função de destino com o código real da função, depois de preencher os argumentos nesse sentido.

No entanto, compiladores modernos são muito bons em inlining chamadas de função automaticamente sem qualquer prompt do usuário Quando ajustado para alta otimização. Na verdade, os compiladores são geralmente melhor em determinar o que chama inline para o ganho de velocidade do que os seres humanos são.

funções Declarando inline explicitamente por causa do ganho de desempenho é (quase?) Sempre desnecessário!

Além disso, compiladores podem e vontade ignorar o pedido inline se lhes convém. Compiladores vai fazer isso se uma chamada para a função é impossível inline (ou seja, usando não triviais recursão ou função ponteiros), mas também se a função é simplesmente demasiado grande para um ganho de desempenho significativo.

Uma regra Definição

No entanto, declarando uma função inline usando o inline chave tem outros efeitos , e pode realmente ser necessário para satisfazer a regra de uma definição (ODR): Esta regra na norma afirma que um determinado símbolo pode ser declarado várias vezes, mas só podem ser definidas uma vez C ++. Se o editor de link (= vinculador) encontra várias definições de símbolos idênticos, ele irá gerar um erro.

Uma solução para este problema é ter certeza de que a unidade de compilação não exporta um determinado símbolo, dando-lhe ligação interna, declarando-static.

No entanto, muitas vezes é melhor marcar uma função inline vez. Este informa o vinculador para mesclar todas as definições desta função através de unidades de compilação em uma definição, com um endereço e variáveis ??de função-estático compartilhados.

Como um exemplo, considere o seguinte programa:

// header.hpp
#ifndef HEADER_HPP
#define HEADER_HPP

#include <cmath>
#include <numeric>
#include <vector>

using vec = std::vector<double>;

/*inline*/ double mean(vec const& sample) {
    return std::accumulate(begin(sample), end(sample), 0.0) / sample.size();
}

#endif // !defined(HEADER_HPP)
// test.cpp
#include "header.hpp"

#include <iostream>
#include <iomanip>

void print_mean(vec const& sample) {
    std::cout << "Sample with x̂ = " << mean(sample) << '\n';
}
// main.cpp
#include "header.hpp"

void print_mean(vec const&); // Forward declaration.

int main() {
    vec x{4, 3, 5, 4, 5, 5, 6, 3, 8, 6, 8, 3, 1, 7};
    print_mean(x);
}

Note que ambos os arquivos .cpp incluem o arquivo de cabeçalho e, portanto, a definição da função de mean. Embora o arquivo é salvo com incluem guardas contra a inclusão dupla, isso irá resultar em duas definições da mesma função, embora em diferentes unidades de compilação.

Agora, se você tentar ligar essas duas unidades de compilação - por exemplo, usando o seguinte comando:

⟩⟩⟩ g++ -std=c++11 -pedantic main.cpp test.cpp

você receberá um erro dizendo “duplicado símbolo __Z4meanRKNSt3__16vectorIdNS_9allocatorIdEEEE” (que é o mutilado nome do nosso mean função).

Se, no entanto, que você tire o modificador inline na frente da definição da função, os compila o código e as ligações corretamente.

Função modelos são um caso especial: são sempre em linha, independentemente de se eles foram declarados dessa forma. Isso não significa que o compilador in-line chamadas para eles, mas eles não vão violar ODR. O mesmo é verdadeiro para funções de membro que são definidas dentro de uma classe ou struct.

código embutido funciona como macros em essência, mas é verdadeiro código real, que pode ser otimizado. Muito pequenas funções são frequentemente bom para inlining porque o trabalho necessário para configurar a chamada de função (carregar os parâmetros nos registos adequados) é caro em comparação com a pequena quantidade de trabalho real o método faz. Com inlining, não há necessidade de configurar a chamada de função, porque o código está diretamente "colado em" qualquer método que usa-lo.

Inlining aumenta o tamanho do código, que é a sua principal desvantagem. Se o código é tão grande que não pode caber no cache da CPU, você pode obter grande lentidão. Você só precisa se preocupar com isso, em casos raros, uma vez que não é provável que você está usando um método em tantos lugares do código aumento causaria problemas.

Em resumo, inlining é ideal para acelerar pequenas métodos que são chamados muitas vezes, mas não em muitos lugares (100 lugares ainda é bom, embora - você precisa ir em exemplos bastante extremas para obter qualquer inchaço código significativo).

Edit: como outros já apontaram, inlining é apenas uma sugestão para o compilador. Ele pode livremente ignorá-lo se ele pensa que você está fazendo pedidos estúpidas como inlining um método 25-line enorme.

  • Existe uma grande diferença entre o código "regular" e código embutido?

Sim - código embutido não envolve uma chamada de função, e salvar as variáveis ??de registro para a pilha. Ele usa o espaço programa de cada vez que é 'chamada'. Portanto, em geral leva menos tempo para executar porque não há é ramificação no processador e economizando de Estado, limpeza de caches, etc.

  • É código embutido simplesmente uma "forma" de macros?

Macros e código embutido compartilham similaridades. a grande diferença é que o código embutido é especificamente formatado como uma função para que o compilador, e mantenedores futuro, ter mais opções. Especificamente, pode facilmente ser transformado em uma função se você dizer ao compilador para otimizar o espaço de código, ou um futuro mantenedor acaba expandindo-lo e usá-lo em muitos lugares em seu código.

  • Que tipo de troca deve ser feito ao escolher para inline seu código?

    • Macro: o uso do espaço alta código, execução rápida, difícil de manter se a 'função' é longo
    • Função: baixo uso de espaço de código, mais lento para executar, de fácil manutenção
    • Inline função: o uso do espaço alta código, execução rápida, de fácil manutenção

Note-se que a economia de registo e saltar para a função que ocupam espaço código, para que por muito pequenas funções um inline pode ocupam menos espaço do que uma função.

-Adam

Depende do compilador ...
Digamos que você tenha um compilador mudo. Ao indicar uma função deve ser embutido, ele vai colocar uma cópia do conteúdo da função em cada ocorrência foram ele é chamado.

Vantagem: nenhuma sobrecarga chamada de função (colocando parâmetros, empurrando o PC atual, saltando para a função, etc.). Pode ser importante na parte central de uma grande loop, por exemplo.

Inconveniência:. Infla o binário gerado

É uma macro? Não realmente, porque o compilador ainda verifica o tipo de parâmetros, etc.

E sobre compiladores inteligentes? Eles podem ignorar a directiva em linha, se eles "sentir" a função é demasiado complexo / muito grande. E talvez eles podem automaticamente incorporadas algumas funções triviais, como getters simples / setters.

difere inline de macros no que é uma dica para o compilador (compilador pode decidir não para inline o código!) E macros são fonte de geração de texto do código antes da compilação e, como tal, são "forçados" a ser embutido.

Marcação meios função inline que o compilador tem o opção para incluir no "inline" onde é chamado, se os escolhe compilador para fazê-lo; pelo contrário, uma vontade macro sempre ser expandido no local. Uma função inline terá símbolos de depuração adequadas definidas para permitir que um depurador simbólico para rastrear a origem de onde veio, durante a depuração macros é confuso. Funções inline precisam ser funções válidas, enquanto macros ... bem, não.

A decisão de declarar uma função inline é em grande parte uma troca de espaço - o seu programa será maior se o compilador decide inline (especialmente se não é também estática, caso em que pelo menos um é necessária cópia não-inline para utilização por quaisquer objectos externos); de fato, se a função for grande, isso pode resultar em uma queda no desempenho como menos de seus ataques de código em cache. O aumento de desempenho geral, no entanto, é exatamente isso que você está recebendo livrar da sobrecarga do próprio chamada de função; para uma pequena função chamada como parte de um circuito interno, que é uma troca que faz sentido.

Se você confiar em seu compilador, marcar pequenas funções utilizadas em circuitos internos inline liberalmente; o compilador será responsável por fazer a coisa certa para decidir se deve ou não em linha.

Se você está marcando o seu código como inline em F. E. C ++ você também está dizendo a seu compilador que o código deve ser executado em linha, ie. esse bloco de código será "mais ou menos" ser inserido onde é chamado (eliminando assim a empurrar, popping e salto na pilha). Então, sim ... é recomendável se as funções são adequados para esse tipo de comportamento.

"inline" é como o 2000 do equivalente a "registrar". Não se preocupe, o compilador pode fazer um trabalho melhor de decidir o que optimize do que você pode.

Por inlining, o compilador insere a implementação da função, no ponto de chamada. O que você está fazendo com isso é remover a sobrecarga chamada de função. No entanto, não há garantia de que seus todos os candidatos para inlining vai realmente ser embutido pelo compilador. No entanto, para funções menores, compiladores sempre inline. Então se você tem uma função que é chamado muitas vezes, mas só tem uma quantidade limitada de código - um par de linhas - você poderia beneficiar de inlining, porque a sobrecarga chamada de função pode demorar mais do que a execução da função em si

Um exemplo clássico de um bom candidato para inlining são getters para classes concretas simples.

CPoint
{
  public:

    inline int x() const { return m_x ; }
    inline int y() const { return m_y ; }

  private:
    int m_x ;
    int m_y ;

};

Alguns compiladores (por exemplo VC2005) tem uma opção para inlining agressivo, e você não precisa especificar o 'inline' palavra-chave ao usar essa opção.

Eu não vou reiterar a acima, mas vale a pena notar que as funções virtuais não vai ser embutido como a função chamada é resolvida em tempo de execução.

Inlining geralmente está habilitado no nível 3 de otimização (O3 em caso de GCC). Pode ser uma melhoria de velocidade significativa em alguns casos (quando é possível).

inlining explícita em seus programas pode adicionar um pouco de melhoria de velocidade com o custo de um tamanho de código incresed.

Você deve ver o que é adequado:. Tamanho do código ou a velocidade e decidir o tempo que você deve incluí-lo em seus programas

Você pode simplesmente virar no nível 3 de otimização e esquecê-la, deixando o compilador fazer o seu trabalho.

A resposta de que você deve linha se resume a velocidade. Se você estiver em um loop chamar uma função, e não é um super enorme função, mas um onde uma grande parte do tempo é desperdiçado em chamar a função, em seguida, fazer essa função inline e você vai ter um monte de estrondo para o seu dinheirinho.

Primeiro de tudo em linha é um pedido para compilador para inline a função .so é até compilador para fazê-lo em linha ou não.

  1. Quando usar? Sempre que uma função é de muito poucas linhas (para todos os assessores e modificador), mas não para recursiva funções
  2. Advantage? Tempo necessário para invocar a chamada de função não está envolvido
  3. É compilador em linha qualquer função própria? Sim sempre que uma função é definida no arquivo de cabeçalho dentro de uma classe

inlining é uma técnica para aumentar a velocidade. Mas usar um profiler para testar esta na sua situação. Eu descobri (MSVC) que inlining nem sempre entregar e certamente não de qualquer maneira espetacular. Runtimes às vezes diminuiu por alguns por cento, mas em circunstâncias ligeiramente diferentes aumentaram uns poucos por cento.

Se o código está sendo executado lentamente, sair de seu profiler para encontrar locais de conflito e trabalhar sobre essas.

Eu parei adicionando funções embutidas em arquivos de cabeçalho, aumenta o acoplamento, mas dá pouco em troca.

código embutido é mais rápido. Não há necessidade de realizar uma chamada de função (cada chamada de função custa algum tempo). Desvantagem é que você não pode passar um ponteiro para uma função incorporada ao redor, como a função não existe realmente como a função e, portanto, não tem ponteiro. Além disso, a função não pode ser exportado para o público (por exemplo, uma função incorporada de uma biblioteca não está disponível dentro binários de ligação contra a biblioteca). Outra é que a seção de código em seu binário vai crescer, se você chamar a função de vários lugares (como cada vez que uma cópia da função é gerado em vez de ter apenas uma cópia e sempre pulando lá)

Normalmente, você não tem que decidir manualmente se uma função deve ser embutido ou não. Por exemplo. GCC vai decidir que automaticamente dependendo otimizar nível (ox) e dependendo de outros parâmetros. Vai levar coisas em consideração como "Quão grande é a função?" (Número de instruções), como muitas vezes é chamado de dentro do código, quanto o binário vai ficar maior por inlining-lo, e algumas outras métricas. Por exemplo. se uma função é estático (portanto, não exportado de qualquer maneira) e apenas chamado uma vez dentro de seu código e você nunca usar um ponteiro para a função, as chances são boas que GCC vai decidir para inline-lo automaticamente, uma vez que não terá nenhum impacto negativo (o binário não vai ficar maior por inlining-lo apenas uma vez).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top