Question

J'ai une bibliothèque C avec de nombreuses routines mathématiques pour gérer les vecteurs, les matrices, les quaternions, etc. Il doit rester en C, car je l’utilise souvent pour des travaux embarqués et en tant qu’extension de Lua. De plus, j'ai des wrappers de classes C ++ qui permettent une gestion plus pratique des objets et une surcharge d'opérateurs pour les opérations mathématiques utilisant l'API C. Le wrapper consiste uniquement en un fichier d’en-tête et permet une utilisation optimale de la doublure.

Existe-t-il une pénalité appréciable pour intégrer le code C au lieu de porter et d'inclure directement l'implémentation dans la classe C ++? Cette bibliothèque est utilisée dans les applications critiques. Alors, l’élimination de l’indirection induite compensera-t-elle le casse-tête lié à la maintenance causé par deux ports?

Exemple d'interface C:

typedef float VECTOR3[3];

void v3_add(VECTOR3 *out, VECTOR3 lhs, VECTOR3 rhs);

Exemple de wrapper 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...
};
Était-ce utile?

La solution

Votre enveloppe elle-même sera intégrée, mais vos appels de méthode à la bibliothèque C ne le seront généralement pas. (Cela nécessiterait des optimisations du temps de liaison qui sont techniquement possibles, mais AFAIK est rudimentaire au mieux dans les outils actuels)

En général, un appel de fonction en tant que tel n’est pas très coûteux. Le coût du cycle a considérablement diminué au cours des dernières années et il est facile à prévoir, de sorte que la pénalité d’appel en tant que telle est négligeable.

Cependant, l'inline ouvre la porte à plus d'optimisations: si vous avez v = a + b + c, votre classe wrapper force la génération de variables de pile, alors que pour les appels insérés, la majorité des données peuvent être conservées dans la FPU. empiler. De plus, le code en ligne permet de simplifier les instructions en tenant compte des valeurs constantes, etc.>

Ainsi, bien que la règle sur la mesure préalable à votre investissement soit respectée, je m'attends à des améliorations possibles.

Une solution typique consiste à amener l’implémentation C dans un format lui permettant d’être utilisée en tant que fonctions inline ou en tant que "C". corps:

// 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 { ... }

Cela n’a probablement de sens que pour des méthodes sélectionnées avec des corps comparativement simples. Je déplacerais les méthodes vers un espace de noms séparé pour l'implémentation C ++, car vous n'en aurez généralement pas besoin directement.

(Notez que l'inline est juste un indice pour le compilateur, il ne force pas la méthode à être en ligne. Mais c’est bien: si la taille du code d’une boucle interne dépasse le cache d’instructions, la performance en ligne nuit facilement aux performances)

Que le passe / retour par référence puisse être résolu dépend de la force de votre compilateur, j'en ai vu beaucoup où     foo (X * out) force les variables de pile, alors que     X foo () conserve les valeurs dans les registres.

Autres conseils

Si vous encapsulez simplement les appels de la bibliothèque C dans des fonctions de classe C ++ (en d'autres termes, les fonctions C ++ ne font qu'appeler des fonctions C), le compilateur optimisera ces appels de manière à ne pas nuire aux performances.

Comme pour toute question sur les performances, il vous sera demandé de mesurer pour obtenir votre réponse (et c'est la réponse tout à fait correcte).

Mais, en règle générale, vous ne constaterez aucune pénalité de performance pour les méthodes simples en ligne qui peuvent réellement être alignées. En général, une méthode inline qui ne fait que passer l’appel à une autre fonction est un excellent candidat pour l’inline.

Cependant, même si vos méthodes d'encapsulation n'étaient pas en ligne, je suppose que vous ne remarqueriez aucune perte de performances, même mesurable, à moins que la méthode d'encapsulation soit appelée dans une boucle critique. Même dans ce cas, cela ne serait probablement mesurable que si la fonction encapsulée elle-même ne faisait pas beaucoup de travail.

Ce genre de chose est la dernière chose à laquelle il faut s’inquiéter. Commencez par vous préoccuper de rendre votre code correct, facile à gérer et d’utiliser les algorithmes appropriés.

Comme d'habitude avec tout ce qui concerne l'optimisation, la réponse est que vous devez mesurer la performance elle-même avant de savoir si l'optimisation en vaut la peine.

  • Testez deux fonctions différentes, l'une appelant directement les fonctions de style C et l'autre l'appelant via l'encapsuleur. Voyez laquelle est la plus rapide ou si la différence se situe dans la marge d'erreur de votre mesure (cela signifierait qu'il n'y a aucune différence que vous pouvez mesurer).
  • Regardez le code d'assemblage généré par les deux fonctions à l'étape précédente (sur gcc, utilisez -S ou -save-temps ). Voyez si le compilateur a fait quelque chose de stupide ou si vos wrappers ont un bogue de performance.

À moins que la différence de performances ne soit trop importante en faveur de la non utilisation de l’emballage, la réimplémentation n’est pas une bonne idée, car vous risqueriez d’introduire des bogues (qui pourraient même entraîner des résultats sains mais qui sont erronés). Même si la différence est grande, il serait plus simple et moins risqué de se rappeler que le C ++ est très compatible avec le C et d'utiliser votre bibliothèque dans le style C même dans le code C ++.

Je ne pense pas que vous remarquerez beaucoup de différence. En supposant que votre plate-forme cible prenne en charge tous vos types de données,

Je suis en train de coder pour la DS et quelques autres dispositifs ARM et les points flottants sont diaboliques ... Je devais taper float vers float vers FixedPoint <16,8

.

Si vous craignez que la surcharge des fonctions d’appel ne vous ralentisse, pourquoi ne pas tester l’alignement du code C ou le transformer en macros?

Aussi, pourquoi ne pas améliorer l'exactitude de const du code C alors que vous y êtes - const_cast doit être utilisé avec parcimonie, en particulier sur les interfaces que vous contrôlez.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top