Contexte / Aperçu
Les opérations sur les variables automatiques ("à partir de la pile", qui sont des variables que vous créez sans appeler malloc
/ new
) sont généralement beaucoup plus rapides que celles impliquant le magasin libre ("le tas", qui sont des variables créées à l'aide de new
). Cependant, la taille des tableaux automatiques est fixée au moment de la compilation, mais la taille des tableaux du magasin gratuit ne l'est pas. De plus, la taille de la pile est limitée (généralement quelques Mio), alors que le stockage gratuit n'est limité que par la mémoire de votre système.
SSO est l'optimisation des chaînes courtes / petites. A std::string
stocke généralement la chaîne en tant que pointeur vers le magasin gratuit ("le tas"), qui donne des caractéristiques de performances similaires à celles que vous appeliez new char [size]
. Cela évite un débordement de pile pour les très grandes chaînes, mais cela peut être plus lent, en particulier avec les opérations de copie. En tant qu'optimisation, de nombreuses implémentations std::string
créent un petit tableau automatique, quelque chose comme char [20]
. Si vous avez une chaîne de 20 caractères ou moins (dans cet exemple, la taille réelle varie), elle la stocke directement dans ce tableau. Cela évite du tout le besoin d'appeler new
, ce qui accélère un peu les choses.
ÉDITER:
Je ne m'attendais pas à ce que cette réponse soit aussi populaire, mais puisque c'est le cas, permettez-moi de donner une implémentation plus réaliste, avec la mise en garde que je n'ai jamais lu une implémentation de SSO "dans la nature".
Détails d'implémentation
Au minimum, a std::string
doit stocker les informations suivantes:
- La taille
- La capacité
- L'emplacement des données
La taille peut être stockée comme un std::string::size_type
ou comme un pointeur vers la fin. La seule différence est de savoir si vous souhaitez avoir à soustraire deux pointeurs lorsque l'utilisateur appelle size
ou ajouter un size_type
à un pointeur lorsque l'utilisateur appelle end
. La capacité peut également être stockée dans les deux sens.
Vous ne payez pas ce que vous n'utilisez pas.
Tout d'abord, considérez l'implémentation naïve basée sur ce que j'ai décrit ci-dessus:
class string {
public:
// all 83 member functions
private:
std::unique_ptr<char[]> m_data;
size_type m_size;
size_type m_capacity;
std::array<char, 16> m_sso;
};
Pour un système 64 bits, cela signifie généralement qu'il std::string
a 24 octets de «surcharge» par chaîne, plus 16 autres pour le tampon SSO (16 choisis ici au lieu de 20 en raison des exigences de remplissage). Cela n'aurait pas vraiment de sens de stocker ces trois membres de données plus un tableau local de caractères, comme dans mon exemple simplifié. Si m_size <= 16
, alors je vais mettre toutes les données m_sso
, donc je connais déjà la capacité et je n'ai pas besoin du pointeur vers les données. Si m_size > 16
, alors je n'ai pas besoin m_sso
. Il n'y a absolument aucun chevauchement là où j'ai besoin de tous. Une solution plus intelligente qui ne gaspille aucun espace ressemblerait un peu plus à ceci (non testé, à titre d'exemple uniquement):
class string {
public:
// all 83 member functions
private:
size_type m_size;
union {
class {
// This is probably better designed as an array-like class
std::unique_ptr<char[]> m_data;
size_type m_capacity;
} m_large;
std::array<char, sizeof(m_large)> m_small;
};
};
Je suppose que la plupart des implémentations ressemblent davantage à ceci.
std::string
implémentée", et une autre demande "qu'est-ce que SSO signifie", vous devez être absolument insensé pour les considérer comme la même question