70 votes

Pourquoi la construction de std::optional<int> est-elle plus coûteuse que celle d'une std::pair<int, bool> ?

Considérez ces deux approches qui peuvent représenter une "option". int " :

using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;

Étant donné ces deux fonctions...

auto get_std_optional_int() -> std_optional_int 
{
    return {42};
}

auto get_my_optional() -> my_optional_int 
{
    return {42, true};
}

...les deux g++ tronc et tronc clang++ (avec -std=c++17 -Ofast -fno-exceptions -fno-rtti ) produire l'assemblage suivant :

get_std_optional_int():
        mov     rax, rdi
        mov     DWORD PTR [rdi], 42
        mov     BYTE PTR [rdi+4], 1
        ret

get_my_optional():
        movabs  rax, 4294967338 // == 0x 0000 0001 0000 002a
        ret

exemple en direct sur godbolt.org


Pourquoi est-ce que get_std_optional_int() nécessitent trois mov les instructions, tandis que get_my_optional() n'a besoin que d'un seul movabs ? S'agit-il d'un problème de qualité d'interface utilisateur, ou y a-t-il quelque chose dans std::optional qui empêche cette optimisation ?

Veuillez également noter que les utilisateurs des fonctions peuvent être complètement optimisés de toute façon :

volatile int a = 0;
volatile int b = 0;

int main()
{
    a = get_std_optional_int().value();
    b = get_my_optional().first;
}

...résulte en :

main:
        mov     DWORD PTR a[rip], 42
        xor     eax, eax
        mov     DWORD PTR b[rip], 42
        ret

6 votes

optional est retourné par un pointeur caché, ce qui signifie que la définition du type contient quelque chose qui interdit de le retourner par un registre.

1 votes

La différence évidente est que std::pair est un agrégat, tandis que std::optional ne l'est pas. Je ne sais pas si ça devrait avoir un effet, mais vous savez...

0 votes

Non. optional doivent allouer un espace de stockage correctement dimensionné et aligné, puis utiliser le nouveau placement pour construire réellement la valeur ?

44voto

Casey Points 18217

Libstdc++ n'implémente apparemment pas P0602 "La variante et l'optionnel doivent propager la trivialité du copier/déplacer". . Vous pouvez le vérifier avec :

static_assert(std::is_trivially_copyable_v<std::optional<int>>);

qui échoue pour libstdc++, et passe pour libc++ et la bibliothèque standard MSVC (qui a vraiment besoin d'un nom propre pour que nous n'ayons pas à l'appeler "L'implémentation MSVC de la bibliothèque standard C++" ou "La STL MSVC").

Bien sûr, MSVC toujours ne passera pas un optional<int> dans un registre car le MS ABI.

EDIT : Ce problème a été corrigé dans la série de versions GCC 8.

0 votes

optional nécessite cependant un destructeur, ce qui empêche de le renvoyer dans les registres.

6 votes

@MaximEgorushkin il n'a besoin d'un destructeur que si T en a un.

1 votes

Ce document a-t-il été adopté ? Il n'est pas reflété dans le dernière version .

19voto

Jester Points 15625

Pourquoi est-ce que get_std_optional_int() nécessitent trois mov les instructions, tandis que get_my_optional() n'a besoin que d'un seul movabs ?

La cause directe est que optional est retourné par un pointeur caché tandis que pair est retourné dans un registre. Mais pourquoi ? La spécification ABI de SysV, section 3.2.3 Passage de paramètres dit :

Si un objet C++ possède un constructeur de copie non trivial ou un destructeur non trivial, il est transmis par référence invisible. destructeur non trivial, il est transmis par référence invisible.

Démêler le désordre du C++ qui est optional n'est pas facile, mais il semble y avoir une constructeur de copie non trivial, au moins dans la version optional_base de l'implémentation que j'ai vérifiée .

3 votes

Il n'est pas nécessaire qu'il le soit, mais il pourrait l'être, et c'est ce qui compte pour le compilateur. Donc vous devez vraiment regarder l'implémentation.

15voto

Maxim Yegorushkin Points 29380

Sur Conventions d'appel pour différents compilateurs C++ et systèmes d'exploitation par Agner Fog il est dit qu'un constructeur ou destructeur de copie empêche de retourner une structure dans les registres. Ceci explique pourquoi optional n'est pas retourné dans les registres.

Il doit y avoir autre chose qui empêche le compilateur de faire la fusion des magasins ( fusionne les mémoires contiguës de valeurs immédiates plus étroites qu'un mot en moins de mémoires plus larges afin de réduire le nombre d'instructions. )... Mise à jour : bogue gcc 82434 - -fstore-merging ne fonctionne pas de manière fiable.

0 votes

Une idée de ce que pourrait être ce "quelque chose d'autre" ?

0 votes

X86-64 ne dispose pas d'un mov mem, imm64 donc vous ne pouvez pas fusionner les magasins.

0 votes

@Jester Il pourrait charger rax avec la valeur fusionnée et la stocker dans [rdi] .

3voto

Kevin Points 15

L'optimisation est techniquement autorisée même avec std::is_trivially_copyable_v<std::optional<int>> étant faux. Cependant, cela peut nécessiter un degré déraisonnable d'"intelligence" de la part du compilateur pour le trouver. Aussi, pour le cas spécifique de l'utilisation de std::optional comme type de retour d'une fonction, l'optimisation peut devoir être effectuée au moment de la liaison plutôt qu'au moment de la compilation.

L'exécution de cette optimisation n'aurait aucun effet sur le comportement observable d'un programme (bien défini)* et est donc implicitement autorisée dans le cadre de l'initiative règle "as-if . Cependant, pour des raisons qui sont expliquées dans d'autres réponses, le compilateur n'a pas été explicitement mis au courant de ce fait et devrait le déduire à partir de zéro. L'analyse statique comportementale est intrinsèquement difficile Le compilateur peut donc ne pas être en mesure de prouver que cette optimisation est sûre en toutes circonstances.

En supposant que le compilateur puisse trouver cette optimisation, il devrait alors modifier la convention d'appel de cette fonction (c'est-à-dire changer la façon dont la fonction renvoie une valeur donnée), ce qui doit normalement être fait au moment de la liaison car la convention d'appel affecte tous les sites d'appel. Le compilateur peut aussi mettre la fonction entièrement en ligne, ce qui n'est pas forcément possible au moment de la compilation. Ces étapes ne seraient pas nécessaires avec un objet trivialement copiable, donc en ce sens, la norme inhibe et complique l'optimisation.

std::is_trivially_copyable_v<std::optional<int>> devrait être vrai. Si c'était vrai, il serait beaucoup plus facile pour les compilateurs de découvrir et d'effectuer cette optimisation. Donc, pour répondre à votre question :

S'agit-il d'un problème de qualité d'interface utilisateur, ou y a-t-il quelque chose dans std::optional qui empêche cette optimisation ?

C'est les deux. La spécification rend l'optimisation beaucoup plus difficile à trouver, et l'implémentation n'est pas assez "intelligente" pour la trouver sous ces contraintes.


* En supposant que tu n'as pas fait quelque chose de vraiment bizarre, comme #define int something_else .

1 votes

Vous incluez la convention d'appel comme faisant partie de "l'implémentation". C'est techniquement vrai, mais à moins d'activer l'optimisation de l'ensemble du programme/du lien, ce n'est pas quelque chose que les compilateurs d'une plate-forme donnée essaieront de changer autrement qu'en faisant un inlining complet ou en créant des versions clonées des fonctions à propagation constante.

0 votes

Je suppose que gcc pourrait émettre quelque chose comme .gcc_reg_return_get_std_optional_int.clone123 ainsi qu'une définition normale conforme à l'ABI. Les appelants d'autres unités de traduction ne peuvent cependant pas supposer que cette définition est présente, ils doivent donc appeler la version normale (à moins que vous n'utilisiez LTO, auquel cas elle sera simplement mise en ligne parce qu'elle est petite). Mais si la fonction était réellement grande, alors il est certain que le clonage d'une version alternative à la convention d'appel de la fonction serait utile. Le plus utile est probablement de renvoyer les parties dans 2 regs séparés, au lieu de les emballer dans RAX.

0 votes

@PeterCordes : Vous êtes sûr ? L'implémentation de la fonction semble complètement hors de propos ici, seule l'implémentation de std::optional devrait être nécessaire et, comme il s'agit d'un modèle, sa mise en œuvre est toujours disponible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X