Como sincronizar as bibliotecas C & C ++ com penalidade de desempenho mínimo?
-
08-07-2019 - |
Pergunta
Eu tenho uma biblioteca C com inúmeras rotinas de matemática para lidar com vetores, matrizes, quaternions e assim por diante. Ele precisa permanecer em C porque muitas vezes eu usá-lo para o trabalho incorporado e como uma extensão Lua. Além disso, tenho ++ invólucros de classe C para permitir uma gestão objeto mais conveniente e sobrecarga de operador para operações matemáticas usando a API C. O invólucro consiste unicamente em um arquivo de cabeçalho e tanto o uso em inlining é feito possível.
Existe uma penalidade apreciável para envolver o código C contra portar e inlining a implementação diretamente na classe C ++? Esta biblioteca é utilizada em aplicações críticas de tempo. Então, faz o impulso de eliminar engano compensar a dor de cabeça manutenção de duas portas?
Exemplo de interface de C:
typedef float VECTOR3[3];
void v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);
Exemplo de invólucro C ++:
class Vector3
{
private:
VECTOR3 v_;
public:
// copy constructors, etc...
Vector3& operator+=(const Vector3& rhs)
{
v3_add(&this->v_, this->v_, const_cast<VECTOR3> (rhs.v_));
return *this;
}
Vector3 operator+(const Vector3& rhs) const
{
Vector3 tmp(*this);
tmp += rhs;
return tmp;
}
// more methods...
};
Solução
O seu próprio invólucro será embutido, no entanto, as suas chamadas de método para a biblioteca C normalmente não vai. (Isso exigiria link-time-otimizações que são tecnicamente possível, mas AFAIK rudimentar na melhor das hipóteses em ferramentas de hoje)
Geralmente, uma chamada de função, como tal, não é muito caro. O custo do ciclo diminuiu consideravelmente nos últimos anos, e isso pode ser previsto com facilidade, por isso a pena a chamada como tal é insignificante.
No entanto, inlining abre a porta para mais otimizações: se você tem v = a + b + c, sua classe de mensagens publicitárias forças a geração de variáveis ??de pilha, enquanto que para as chamadas inlined, a maioria dos dados pode ser mantido na FPU pilha. Além disso, o código embutido permite simplificar as instruções, considerando valores constantes, e muito mais.
Assim, enquanto o medida antes de investir regra vale, eu esperaria algum espaço para melhorias aqui.
Uma solução típica é trazer o implementaiton C para um formato que pode ser usado quer como funções em linha ou como "C" corpo:
// V3impl.inl
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs)
{
// here you maintain the actual implementations
// ...
}
// C header
#define V3DECL
void V3DECL v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);
// C body
#include "V3impl.inl"
// CPP Header
#define V3DECL inline
namespace v3core {
#include "V3impl.inl"
} // namespace
class Vector3D { ... }
Isso provavelmente faz sentido apenas para os métodos selecionados com corpos simples comparedly. Eu mover os métodos para um namespace separado para implementação do C ++, como você geralmente não precisa-los diretamente.
(Note que a linha é apenas uma sugestão compilador, ele não força o método a ser embutido. Mas isso é bom: se o tamanho do código de um circuito interno excede o cache de instruções, inlining facilmente dói desempenho)
Se o passe / retorno por referência podem ser resolvidos depende da força de seu compilador, eu vi muitos onde foo (X * a) forças empilhar variáveis, enquanto X foo () faz valores manter-se em registros.
Outras dicas
Se você está apenas envolvendo as chamadas de biblioteca C em funções de classe C ++ (em outras palavras, as funções C ++ fazer nada além de funções de chamada C), em seguida, o compilador irá otimizar essas chamadas para que ele não é uma penalidade de desempenho.
Tal como acontece com qualquer pergunta sobre o desempenho, você vai ser dito para medir a obter a sua resposta (e essa é a resposta estritamente correto).
Mas, como regra geral, por métodos in-line simples que pode realmente ser embutido, você verá nenhuma penalidade de desempenho. Em geral, um método em linha que não faz nada, mas passar a chamada para outra função é um grande candidato para inlining.
No entanto, mesmo se os seus métodos de mensagens publicitárias não foram inline, eu suspeito que você iria notar nenhuma penalidade de desempenho - nem mesmo um mensurável - a menos que o método de embalagem estava sendo chamado em algum circuito crítico. Mesmo assim, provavelmente só iria ser mensuráveis ??se a função embrulhada em si não fazer muito trabalho.
Este tipo de coisa é a última coisa para se preocupar. Primeiro preocupar em fazer o seu código correto, sustentável, e que você está usando algoritmos apropriados.
Como de costume com tudo relacionado à otimização, a resposta é que você tem que medir o próprio desempenho antes de saber se a otimização vale a pena.
- benchmark duas funções diferentes, um chamando as funções de estilo C diretamente e outra chamada através do invólucro. Ver qual corre mais rápido, ou se a diferença está dentro da margem de erro da medição (o que significa que não há diferença que você pode medir).
- olhar para o código gerado pelo conjunto das duas funções no passo anterior (em gcc, uso ou
-S
-save-temps
). Veja se o compilador fez algo estúpido, ou se os seus invólucros ter qualquer bug desempenho.
A menos que a diferença de desempenho é muito grande em favor de não usar o wrapper, reimplementar não é uma boa idéia, já que corre o risco de erros introduzindo (o que poderia até mesmo causar resultados que sane olhar, mas estão errados). Mesmo que a diferença é grande, seria mais simples e menos arriscada para lembre-C ++ é muito compatível com C e usar sua biblioteca no estilo C, mesmo no código C ++.
Eu não acho que você vai notar muita diferença perf. Assumindo seu apoio plataforma de destino todos os seus tipos de dados,
Eu estou de codificação para o DS e alguns outros dispositivos ARM e flutuantes pontos são maus ... Eu tive que typedef float para FixedPoint <16,8>
Se você está preocupado que a sobrecarga de chamar funções está retardando para baixo, por que não testar inlining o código C ou transformá-lo em macros?
Além disso, por que não melhorar a precisão const do código C, enquanto você está nisso -. Const_cast realmente deve ser usado com moderação, especialmente em interfaces que você controla