8
votes

Comment puis-je forcer le constructeur de copie généré par le compilateur d'une classe à * non * être inlincé par le compilateur?

Le titre de la question alternative serait: Comment explicitement le compilateur générer du code pour les constructeurs générés par le compilateur dans une unité de traduction spécifique? em>

Le problème que nous sommes confrontés est que pour un chemin de code le résultat - complètement mesuré - La performance est meilleure (d'environ 5%) si les appels COPY-CTOR d'un objet sont pas em> inliné, c'est-à-dire si ce constructeur est mis en œuvre manuellement. (Nous avons remarqué cela parce que pendant le nettoyage de code, la copie expressée superflue de cette classe (17 membres) a été supprimée.) P>

EDIT: STRON> Notez que nous avons / EM> Vérifié le code de montage généré et s'est assuré que la génération d'inlinage et de code se produit comme je décris pour les deux versions de code différentes. p>

Nous faisons face maintenant au choix de simplement déposer le code manuel COPY-CTOR en arrière (il fait exactement la même chose que le compilateur généré un) ou la recherche de tout autre moyen de pas em> inlindicing la copie cors de cette classe. p>

existe un moyen (pour Microsoft Visual C ++) pour instancier explicitement les fonctions de classe générée par compilateur dans une unité de traduction spécifique ou être toujours inline dans chaque unité de traduction où elles sont utilisé? (Les commentaires pour GCC ou d'autres compilateurs sont également les bienvenus pour obtenir une meilleure image de la situation.) P>


Comme les 2 premières réponses montrent un malentendu: le compilateur généré em> classe Les fonctions ne sont générées que par le compilateur lui-même si elles ne sont ni déclarées ni définies par l'utilisateur. Par conséquent, aucun modificateur que ce soit ne peut être appliqué à eux, car ces fonctions n'existent pas em> dans le code Sourcecode. P> xxx pré>

A code> A une valeur par défaut et copie CTOR, un DTOR et un opérateur de copie. Aucune de ces fonctions ne peut être modifiée via certaines DeclSpec car elles n'existent pas dans le code. P>

struct B {
  std::string member;
  B(B const& rhs);
};


7 commentaires

Oh, quel maux de tête! :-)


@Martin: C'est assez rare que la blessure de performance en ligne (généralement vous gagnez rapidement, car vous n'avez pas d'appel de fonctions et de sa configuration, etc.). Même si de la chance: /


Je ne vois pas ce qu'enlinge a à voir avec ça. Avez-vous vérifié que la version générée par le compilateur est inlinée et que vous avez explicitement mis en œuvre, ne le fait pas? Ou êtes-vous simplement de mélanger la terminologie, en utilisant "Inline" pour faire référence à quelque chose d'autre (généré par compilateur, peut-être) - et avez-vous vérifié en regardant dans l'assemblage généré que les celles générées et définies manuellement sont exactes la même chose ?


Essayez de compiler pour la taille au lieu de la vitesse.


Avez-vous essayé de le déclarer dans la définition de la classe, puis définissez-le dans votre unité de translation préférée, à l'aide de C ++ 0x = défaut ? C'est "essayé" à la fois au sens de ", cela affecte-t-il la vitesse?", Et aussi dans le sens de ", cela compilait-il, c'est-à-dire que votre version de MSVC prend en charge la fonctionnalité C ++ 0X?".


@Steve - Malheureusement no c ++ 0x ici.


@Steve: Même MSVC2010 ne prend pas en charge = défaut , malheureusement.


6 Réponses :


0
votes

__declSpec (NOINLINE) . < / p>

La documentation indique qu'il s'applique uniquement aux fonctions des membres, mais cela fonctionne en fait avec des fonctions libres.


1 commentaires

Comment pouvez-vous utiliser cela pour une fonction générée par compilateur?



5
votes

12,1 $ / 5- "une déclaration implicitement déclarée Le constructeur par défaut est une inine membre public de sa classe. ".

Donc, il n'y a rien grand chose que nous pouvons faire. Le constructeur implicateur doit être en ligne. Tout autre comportement à cet égard serait probablement une extension

avoir dit que

Il est probable que votre constructeur de copie manuel (que vous avez supprimé pendant le nettoyage du code) faisait la bonne chose. Par exemple, si l'un des membres (sur 17) de votre classe est un membre du pointeur, il est probable que le constructeur de copie manuelle a pris soin de la copie profonde (et a donc pris une performance touchée).

Donc, à moins que vous ne examiniez attentivement votre constructeur de copie manuelle, ne pensez même pas à le supprimer et à compter sur le constructeur de copie implicite (potentiellement buggy) (dans votre contexte)


11 commentaires

Je suis d'accord avec la première partie de cette réponse, mais je ne peux pas voir ce qui vous conduit à l'autre conclusion. Premièrement, si le CC explicite faisait des choses supplémentaires, il est tout à fait improbable qu'il soit plus rapide que l'un implicite. Deuxièmement, OP a écrit que le CC explicite était "superflu", et je ne vois aucune raison de se méfier de la méfiance.


@Gorpik: "La performance est meilleure (d'environ 5%) si les appels COPY-CTOR d'un objet ne sont pas inlinés, c'est-à-dire que si ce constructeur est mis en œuvre manuellement."


Merci pour la citation STD! Et comme je l'ai déjà dit dans la question, la copie explicite CORTOR faisait exactement la bonne chose et l'une implicitement déclarée, c'est aussi exactement la bonne chose et il n'y a pas de problèmes de pointeur - le differ de la performance est vraiment et réellement dû à la ( Multipliez) COTE-CTOR-CORTRÉE enfilé contre la copie-CTOR dans une fonction distincte.


@Chubsdad: Oui, c'est ce que j'ai dit. Si la CCTOR explicite (mise en œuvre manuelle et non inline) fait des travaux supplémentaires, cela ne devrait pas mieux fonctionner.


@Martin: Comment avez-vous conclu que la performance touchée est due à plusieurs CC inlincé?


@Martin: Comme le dit Chubsdad, vous n'avez pas vraiment donné de raison de penser que l'inlinage n'a rien à voir avec la différence def. Peux-tu élaborer?


Eh bien, l'inlinisation pourrait nuire aux performances s'ils sont à la frontière du cache de code L1 ...


Je pense que l'OP est une fonction multiple en ligne dans différentes unités de traduction. Mais autant que je sache, un éditeur de force vraiment industriel optimisera vraiment toutes les copies inlicales redondantes dans toutes les unités de traduction. Mais pas sûr


@CHUBSDAD: Si vous avez plusieurs instanciations d'une fonction, il n'est pas inliné. L'intégralité de l'inlinisation consiste à placer l'instanciation du code sur le site d'appel, copiant efficacement la fonction. Ce n'est pas redondant et vous ne pouvez pas l'optimiser (au moins sans renvoyer l'optimisation d'origine d'origine)


@CHUBSAD, Autres - Voir les modifications pour des informations supplémentaires sur les mesures de performance.


Notez que j'ai depuis découvert que l'instruction standard n'implique pas que le compilateur doit générer un code inlinéré. Voir d'autres modifications.



0
votes

Il est souvent préférable de l'isoler à quelques types de nœuds que vous savez problématiques. Exemple A:

/* B.hpp */

struct B {
private:

    /* class types */
    struct t_data {
        std::string member;

        /* 16 more ... */
    public:
        /* declare + implement the ctor B needs */

        /* since it is otherwise inaccessible, it will only hurt build times to make default ctor/dtor implicit (or by implementing them in the header, of course), so define these explicitly in the cpp file */
        t_data();
        ~t_data();

        /* allow implicit copy ctor and assign -- this could hurt your build times, however. it depends on the complexity/visibility of the implementation of the data and the number of TUs in which this interface is visible. since only one object needs this... it's wasteful in large systems */
    };
private:

    /* class data */
    t_data d_data;
public:
    /* you'll often want the next 4 out of line
       -- it depends on how this is created/copied/destroyed in the wild
     */
    B();
    B(const B& other);
    ~B();
    B& operator=(const B&);
};

/* B.cpp */

/* assuming these have been implemented properly for t_data */
B::B() : d_data() {
}

B::B(const B& other) : d_data(other) {
}

B::~B() {
}

B& B::operator=(const B&) {
    /* assuming the default behaviour is correct...*/
    this->d_data = other.d_data;
    return *this;
}
/* continue to B::t_data definitions */


0 commentaires

3
votes

Je doute fortement l'inlinisation a quelque chose à voir avec ça. Si le compilateur augmente la copie CTOR générée par le compilateur, pourquoi ne serait-ce pas également définie explicitement? (Il est également inhabituel que les heuristiques d'optimisation du compilateur échouent si mal pour rendre un code inliné à 5% plus lent)

Avant de sauter aux conclusions,

  • Vérifiez l'ensemble généré pour vérifier que les deux versions réalisent réellement exactement la même chose (et dans le même ordre, en utilisant le même assemblage, etc., car sinon, ce qui pourrait être la source de votre différence de performance)
  • Vérifiez que le compilateur généré par un est étant inliné, et le défini manuellement est pas . .

    Si tel est le cas, pourriez-vous mettre à jour votre question avec ces informations?

    Il n'y a aucun moyen en C ++ pour indiquer si une fonction générée par le compilateur devrait ou ne doit pas être inlinée. Pas même des extensions spécifiques aux fournisseurs telles que __ DeclSpec (NOINLINE) vous aideront, puisque vous remettez explicitement la responsabilité de la fonction au compilateur. Donc, le compilateur choisit quoi faire avec elle, comment la mettre en œuvre et savoir s'il le fasse ou non. Vous ne pouvez pas tous les deux dire "Veuillez implémenter cette fonction pour moi", et en même temps "Veuillez me permettre de contrôler la manière dont la fonction est mise en œuvre". Si vous souhaitez contrôler la fonction, vous devez la mettre en œuvre. ;)

    in c ++ 0x, il est possible peut être possible (en fonction de la manière dont ces extensions spécifiques au fournisseur interagissent avec des fonctions déclarées comme = par défaut ).

    Mais encore une fois, je ne suis pas convaincu que l'inlinage est la question. Très probablement, les deux fonctions aboutissent à un code de montage différent généré.


4 commentaires

+1. Le compilateur pourrait bien être incapable de faire connaître le CCTOR explicitement écrit s'il s'agit d'un fichier source (par opposition à un fichier d'en-tête), comme je suppose. Mais je suis d'accord avec le raisonnement principal et le reste de la réponse.


@Gorpik: Cela pourrait, mais cela dépend des drapeaux du compilateur. MSVC peut aligner sur les unités de traduction et même .libs avec les bons drapeaux. Et je suppose qu'ils utilisent une optimisation assez agressive s'ils sont préoccupés par une réduction de 5% de la performance.


J'ai modifié la question pour inclure une note que nous avons avoir effectivement vérifié l'Assemblée. Je vous assure que la seule différence entre les deux versions où la performance a été mesurée est celle-ci d'une copie inline et d'une copie de copie non inlinée. (Et oui, la copie manuelle CTOR est Evemioulsy pas en ligne, car elle est implémentée dans un fichier CPP et nous n'utilisons pas l'optimisation de l'ensemble de PRG.)


Intéressant. Dans ce cas, je suis corrigé. :) Je suppose que vous devrez mettre en œuvre explicitement la fonction (ou essayer, comme @gman suggéré dans un autre commentaire, optimisant la taille plutôt que la vitesse)



0
votes

Vous pouvez utiliser une sorte d'objet imbriqué. De cette manière, le constructeur de copie de l'objet imbriqué peut être laissé à titre de défaut sans entretien, mais vous avez toujours un constructeur de copie créé explicitement créé que vous pouvez déclarer NOINLINE.

class some_object_wrapper {
    original_object obj;
    __declspec(noinline) some_object_wrapper(const some_object_wrapper& ref) 
        : obj(ref) {}
    // Other function accesses and such here
};


1 commentaires

"Pourquoi avez-vous mis en œuvre un constructeur de copie manuelle s'il fait la même chose que le constructeur de copie par défaut?" ... Je devais rire de ça .... Avez-vous déjà érayé par le code hérité? La quantité de programmation de cargaison que vous pouvez trouver est simplement stupéfiante :-)



0
votes

Ajouter ma propre conclusion et répondre à la question exacte sans entrer dans les détails:

  1. vous ne peut pas force le compilateur, spécifiquement VC ++, en ligne ou non en ligne un CTOR / DTOR généré par le compilateur / etc. - mais

  2. L'optimiseur choisira - à sa discrétion - s'il englobe le code d'une fonction générée par compilateur (CTOR) ou s'il génère une fonction "réelle" pour ce code. AFAIK Il n'y a aucun moyen d'influencer la décision de l'optimiseur à cet égard.


0 commentaires