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
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.
Pourquoi
get_std_optional_int()
nécessite-t-il trois instructionsmov
, alors queget_my_optional()
n'a besoin que d'une seulemovabs
?
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 .
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.
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
.