web-dev-qa-db-fra.com

Quels sont les mécanismes d'optimisation des chaînes courtes dans libc ++?

Cette réponse donne une vue d’ensemble de Nice 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 de libc ++:

  • Combien de temps faut-il pour que la chaîne soit qualifiée pour le SSO? Cela dépend-il de l'architecture cible?

  • Comment l'implémentation fait-elle la distinction entre les chaînes courtes et les chaînes longues lors de l'accès aux données de chaîne? Est-ce aussi simple que m_size <= 16 ou s'agit-il d'un drapeau faisant partie d'une autre variable membre? (J'imagine que m_size ou une partie de celui-ci peut é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 l'authentification unique, c'est même mentionné sur le page d'accueil de libc ++ .

Voici quelques observations après avoir regardé la source :

libc ++ peut être compilé avec deux dispositions de mémoire légèrement différentes pour la classe string, elle est régie par le _LIBCPP_ALTERNATE_STRING_LAYOUT drapeau. Les deux configurations distinguent également les machines little-endian et big-endian, ce qui nous laisse au total 4 variantes. Je vais assumer la mise en page "normale" et le petit endian dans ce qui suit.

En supposant en outre que size_type est 4 octets et que value_type est 1 octet, voici à quoi ressemblent 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

Puisque la taille de la chaîne courte se trouve dans les 7 bits supérieurs, il faut la déplacer 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 peu.

Je cherche toujours une réponse à ma première question, c’est-à-dire quelle valeur aurait __min_cap, la capacité des chaînes courtes, prendre pour différentes architectures?

Autres implémentations de bibliothèque standard

Cette réponse donne un bon aperçu de std::string dispositions de mémoire dans d'autres implémentations de bibliothèque standard.

90
ValarDohaeris

La libc ++ basic_string Est conçue pour avoir un sizeof 3 mots sur toutes les architectures, où sizeof(Word) == sizeof(void*). Vous avez correctement disséqué l'indicateur long/short et le champ de taille dans le formulaire court.

quelle valeur __min_cap, la capacité des chaînes courtes, prend-elle pour différentes architectures?

Dans la forme abrégée, il y a 3 mots avec lesquels travailler:

  • 1 bit va au drapeau long/short.
  • 7 bits vont à la taille.
  • En supposant que char, 1 octet va au null final (libc ++ stockera toujours un null final derrière les données).

Cela laisse 3 mots moins 2 octets pour stocker une chaîne courte (c'est-à-dire le plus grand capacity() sans allocation).

Sur une machine 32 bits, 10 caractères vont tenir dans la chaîne courte. sizeof (chaîne) est 12.

Sur une machine 64 bits, 22 caractères vont tenir 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 raison en est d’accélérer la construction et l’affectation des déménagements. Plus le sizeof est grand, plus vous devez déplacer de mots pendant une construction ou une affectation de déplacement.

La forme longue nécessite un minimum de 3 mots pour stocker le pointeur de données, sa taille et sa capacité. Par conséquent, j'ai limité la forme abrégée à 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 design.

_ 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 sorte que la "mise en page longue" passe de:

struct __long
{
    size_type __cap_;
    size_type __size_;
    pointer   __data_;
};

à:

struct __long
{
    pointer   __data_;
    size_type __size_;
    size_type __cap_;
};

La motivation de ce changement est la conviction que le fait de placer __data_ En premier aura certains avantages en termes de performances grâce à un meilleur alignement. Une tentative a été faite pour mesurer les avantages de performance, et c'était difficile à mesurer. Cela ne fera pas empirer les performances et pourrait l’améliorer légèrement.

Le drapeau doit être utilisé avec précaution. C'est une ABI différente, et si elle est accidentellement mélangée avec une libc ++ std::string Compilée avec un paramètre différent de _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT, Cela créera des erreurs d'exécution.

Je recommande que cet indicateur ne soit modifié que par un fournisseur de libc ++.

108
Howard Hinnant

Le implémentation de libc ++ est un peu compliqué, je vais ignorer sa conception alternative et supposer un petit ordinateur endian:

template <...>
class basic_string {
/* many many things */

    struct __long
    {
        size_type __cap_;
        size_type __size_;
        pointer   __data_;
    };

    enum {__short_mask = 0x01};
    enum {__long_mask  = 0x1ul};

    enum {__min_cap = (sizeof(__long) - 1)/sizeof(value_type) > 2 ?
                      (sizeof(__long) - 1)/sizeof(value_type) : 2};

    struct __short
    {
        union
        {
            unsigned char __size_;
            value_type __lx;
        };
        value_type __data_[__min_cap];
    };

    union __ulx{__long __lx; __short __lxx;};

    enum {__n_words = sizeof(__ulx) / sizeof(size_type)};

    struct __raw
    {
        size_type __words[__n_words];
    };

    struct __rep
    {
        union
        {
            __long  __l;
            __short __s;
            __raw   __r;
        };
    };

    __compressed_pair<__rep, allocator_type> __r_;
}; // basic_string

Remarque: __compressed_pair Est essentiellement une paire optimisée pour le optimisation de la base vide , aussi appelé template <T1, T2> struct __compressed_pair: T1, T2 {};; à toutes fins pratiques, vous pouvez le considérer comme une paire régulière. Son importance vient tout simplement parce que std::allocator Est sans état et donc vide.

Ok, c'est plutôt brut, alors vérifions la mécanique! En interne, de nombreuses fonctions appellent __get_pointer() qui appelle elle-même __is_long Pour déterminer si la chaîne utilise la représentation __long Ou __short:

bool __is_long() const _NOEXCEPT
    { return bool(__r_.first().__s.__size_ & __short_mask); }

// __r_.first() -> __rep const&
//     .__s     -> __short const&
//     .__size_ -> unsigned char

Pour être honnête, je ne suis pas trop sûr qu'il s'agisse de la norme C++ (je connais la provision initiale de sous-séquence dans union, mais je ne sais pas comment elle se lie à une union anonyme et à un alias jeté ensemble), mais une bibliothèque standard est autorisée. de profiter de toute façon du comportement défini par la mise en œuvre.

19
Matthieu M.