web-dev-qa-db-fra.com

Pourquoi la construction de std :: optional <int> est-elle plus chère qu'une std :: pair <int, bool>?

Considérez ces deux approches qui peuvent représenter un "optionnel int":

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

Compte tenu de ces deux fonctions ...

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

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

... à la fois tronc g ++ et tronc clang ++ ( avec -std=c++17 -Ofast -fno-exceptions -fno-rtti) produisez 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 get_std_optional_int() nécessite-t-il trois mov instructions, alors que get_my_optional() n'a besoin que d'un seul movabs? Est-ce un problème de QoI, ou y a-t-il quelque chose dans la spécification de std::optional Qui empêche cette optimisation?

Veuillez également noter que les utilisateurs des fonctions peuvent être complètement optimisés indépendamment:

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
66
Vittorio Romeo

libstdc ++ n'implémente apparemment pas P0602 "variant et optionnel devrait propager la banalité 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 correct, nous n'avons donc pas à l'appeler "L'implémentation MSVC de la bibliothèque standard C++" ou " Le MSVC STL ").

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

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

43
Casey

Pourquoi get_std_optional_int() nécessite-t-il trois instructions mov, alors que get_my_optional() n'a besoin que d'une seule movabs?

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

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

Trier le désordre C++ qui est optional n'est pas facile, mais il semble y avoir un constructeur de copie non trivial au moins dans le optional_base classe de l'implémentation que j'ai vérifiée .

18
Jester

Dans 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 des registres. Cela explique pourquoi optional n'est pas renvoyé dans les registres.

Il doit y avoir autre chose empêchant le compilateur de fusionner les magasins ( fusionne les magasins contigus de valeurs immédiates plus étroites qu'un Word dans moins de magasins plus larges pour réduire le nombre d'instructions) ... Mise à jour: bogue gcc 82434 - -fstore-merging ne fonctionne pas de manière fiable.

16
Maxim Egorushkin

L'optimisation est techniquement autorisée , même avec std::is_trivially_copyable_v<std::optional<int>> étant faux. Cependant, il peut nécessiter un degré déraisonnable d '"intelligence" pour que le compilateur le trouve. De plus, 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.

La réalisation de cette optimisation n'aurait aucun effet sur le comportement observable d'un programme (bien défini), * et est donc implicitement autorisée sous la règle comme si . Cependant, pour des raisons qui sont expliquées dans d'autres réponses, le compilateur n'a pas été explicitement informé de ce fait et devrait le déduire de zéro. L'analyse statique comportementale est intrinsèquement difficile , donc le compilateur peut 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), qui doit normalement être effectuée au moment de la liaison car la convention d'appel affecte tous les sites d'appel. Alternativement, le compilateur pourrait intégrer la fonction entièrement, ce qui peut ou non être possible au moment de la compilation. Ces étapes ne seraient pas nécessaires avec un objet copiable trivialement, donc dans 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:

Est-ce un problème de QoI ou y a-t-il quelque chose dans std::optional les spécifications empêchant cette optimisation?

C'est les deux. La spécification rend l'optimisation beaucoup plus difficile à trouver et la mise en œuvre n'est pas suffisamment "intelligente" pour la trouver sous ces contraintes.


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

4
Kevin