Cette réponse donne un bel aperçu de haut niveau de l'optimisation des chaînes courtes (SSO). Cependant, j'aimerais savoir plus en détail comment cela fonctionne dans la pratique, en particulier dans l'implémentation libc ++:
Quelle doit être la longueur de la chaîne pour être éligible au SSO? Cela dépend-il de l'architecture cible?
Comment l'implémentation distingue-t-elle les chaînes courtes et longues lors de l'accès aux données de chaîne? Est-ce aussi simple
m_size <= 16
ou s'agit-il d'un indicateur faisant partie d'une autre variable membre? (J'imagine quem_size
ou une partie de celui-ci pourrait également être utilisé pour stocker des données de chaîne).
J'ai posé cette question spécifiquement pour libc ++ parce que je sais qu'il utilise SSO, cela est même mentionné sur la page d'accueil de libc ++ .
Voici quelques observations après avoir regardé la source :
libc ++ peut être compilée avec deux dispositions de mémoire légèrement différentes pour la classe string, ceci est régi par l' _LIBCPP_ALTERNATE_STRING_LAYOUT
indicateur. Les deux configurations distinguent également les machines petit-boutiste et grand-boutiste, ce qui nous laisse un total de 4 variantes différentes. Je vais supposer la mise en page "normale" et little-endian dans ce qui suit.
En supposant en outre que size_type
4 octets et value_type
1 octet, voici à quoi ressembleraient les 4 premiers octets d'une chaîne en mémoire:
// short string: (s)ize and 3 bytes of char (d)ata
sssssss0;dddddddd;dddddddd;dddddddd
^- is_long = 0
// long string: (c)apacity
ccccccc1;cccccccc;cccccccc;cccccccc
^- is_long = 1
Étant donné que la taille de la chaîne courte est dans les 7 bits supérieurs, elle doit être décalée lors de l'accès:
size_type __get_short_size() const {
return __r_.first().__s.__size_ >> 1;
}
De même, le getter et le setter pour la capacité d'une longue chaîne utilisent __long_mask
pour contourner le is_long
bit.
Je cherche toujours une réponse à ma première question, à savoir quelle valeur __min_cap
la capacité des chaînes courtes prendrait pour différentes architectures?
Autres implémentations de bibliothèques standard
Cette réponse donne un bon aperçu des std::string
dispositions de mémoire dans d'autres implémentations de bibliothèques standard.
la source
string
tête ici , je le vérifie en ce moment :)Réponses:
La libc ++
basic_string
est conçue pour avoirsizeof
3 mots sur toutes les architectures, oùsizeof(word) == sizeof(void*)
. Vous avez correctement disséqué le drapeau long / court et le champ de taille dans le formulaire court.Dans la forme courte, il y a 3 mots avec lesquels travailler:
char
, 1 octet va au null de fin (la libc ++ stockera toujours un null de fin derrière les données).Cela laisse 3 mots moins 2 octets pour stocker une courte chaîne (c.-à-d.
capacity()
sans allocation).Sur une machine 32 bits, 10 caractères rentreront dans la chaîne courte. sizeof (chaîne) est 12.
Sur une machine 64 bits, 22 caractères rentreront dans la chaîne courte. sizeof (chaîne) est 24.
Un objectif majeur de la conception était de minimiser
sizeof(string)
, tout en rendant le tampon interne aussi grand que possible. La justification est d'accélérer la construction du déménagement et l'attribution du déménagement. Plus lesizeof
, plus vous devez déplacer de mots pendant une construction de mouvement ou une affectation de mouvement.La forme longue nécessite un minimum de 3 mots pour stocker le pointeur de données, la taille et la capacité. Par conséquent, j'ai limité la forme courte à ces 3 mêmes mots. Il a été suggéré qu'une taille de 4 mots pourrait avoir de meilleures performances. Je n'ai pas testé ce choix de conception.
_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
Il existe un indicateur de configuration appelé
_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
qui réorganise les membres de données de telle sorte que la "mise en page longue" change de:à:
La motivation de ce changement est la conviction que mettre
__data_
avant aura certains avantages en termes de performances en raison d'un meilleur alignement. Une tentative a été faite pour mesurer les avantages de performance, et il était difficile de les mesurer. Cela n'aggravera pas les performances et pourrait les rendre légèrement meilleures.Le drapeau doit être utilisé avec précaution. C'est un ABI différent, et s'il est mélangé accidentellement avec une libc ++
std::string
compilée avec un paramètre différent de_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
créera des erreurs d'exécution.Je recommande que cet indicateur ne soit modifié que par un fournisseur de libc ++.
la source
string
est de 0 bits. Cela rend la construction par défaut très efficace. Et si vous êtes prêt à contourner les règles, parfois même gratuitement. Par exemple, vous pouvezcalloc
utiliser de la mémoire et simplement déclarer qu'elle est pleine de chaînes construites par défaut.int
s afin que la classe puisse être compressée à seulement 16 octets sur des architectures 64 bits?sizeof
. Mais en même temps, le tampon interne pourchar
passe de 14 à 22, ce qui est un très bon avantage.L' implémentation de libc ++ est un peu compliquée, je vais ignorer sa conception alternative et supposer un petit ordinateur endian:
Remarque:
__compressed_pair
est essentiellement une paire optimisée pour l'optimisation de la base vide , akatemplate <T1, T2> struct __compressed_pair: T1, T2 {};
; à toutes fins utiles, vous pouvez le considérer comme une paire régulière. Son importance vient juste parce qu'ellestd::allocator
est apatride et donc vide.D'accord, c'est plutôt brut, alors vérifions la mécanique! En interne, de nombreuses fonctions appelleront
__get_pointer()
qui elle-même appelle__is_long
pour déterminer si la chaîne utilise la représentation__long
ou__short
:Pour être honnête, je ne suis pas trop sûr qu'il s'agisse de C ++ standard (je connais la disposition de sous-séquence initiale dans
union
qu'il s'agisse de mais je ne sais pas comment elle s'accorde avec une union anonyme et un aliasing jeté ensemble), mais une bibliothèque standard est autorisée à tirer parti de l'implémentation définie comportement de toute façon.la source
__min_cap
serait évalué pour différentes architectures, je ne suis pas sûr de cesizeof()
qui reviendra et comment il est influencé par l'aliasing.3 * the size of one pointer
dans ce cas, qui serait de 12 octets sur un arc de 32 bits et de 24 sur un arc de 64 bits.