Sfondo / Panoramica
Le operazioni su variabili automatiche ("dallo stack", che sono variabili create senza chiamare malloc
/ new
) sono generalmente molto più veloci di quelle che coinvolgono l'archivio gratuito ("l'heap", che sono variabili che vengono create utilizzando new
). Tuttavia, la dimensione delle matrici automatiche viene fissata al momento della compilazione, ma non la dimensione delle matrici dal negozio gratuito. Inoltre, le dimensioni dello stack sono limitate (in genere alcuni MiB), mentre l'archivio gratuito è limitato solo dalla memoria del sistema.
SSO è l'ottimizzazione di stringhe corte / piccole. A in std::string
genere memorizza la stringa come puntatore al negozio gratuito ("l'heap"), che offre caratteristiche di prestazione simili a quelle che si dovrebbero chiamare new char [size]
. Ciò impedisce un overflow dello stack per stringhe molto grandi, ma può essere più lento, soprattutto con le operazioni di copia. Come ottimizzazione, molte implementazioni di std::string
creano un piccolo array automatico, qualcosa del genere char [20]
. Se hai una stringa di 20 caratteri o più piccola (dato questo esempio, la dimensione effettiva varia), la memorizza direttamente in quella matrice. Questo evita la necessità di chiamare new
, accelerando un po 'le cose.
MODIFICARE:
Non mi aspettavo che questa risposta fosse così popolare, ma dato che lo è, lasciami dare un'implementazione più realistica, con l'avvertenza che in realtà non ho mai letto nessuna implementazione di SSO "in the wild".
Dettagli di implementazione
Come minimo, è std::string
necessario memorizzare le seguenti informazioni:
- La dimensione
- La capacità
- La posizione dei dati
La dimensione può essere memorizzata come std::string::size_type
o come puntatore alla fine. L'unica differenza è se si desidera sottrarre due puntatori quando l'utente chiama size
o aggiungere size_type
a un puntatore quando l'utente chiama end
. La capacità può essere archiviata in entrambi i modi.
Non paghi per ciò che non usi.
Innanzitutto, considera l'implementazione ingenua in base a ciò che ho descritto sopra:
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;
};
Per un sistema a 64 bit, ciò significa generalmente che std::string
ha 24 byte di "overhead" per stringa, più altri 16 per il buffer SSO (16 scelti qui invece di 20 a causa dei requisiti di riempimento). Non avrebbe davvero senso archiviare quei tre membri di dati più una matrice locale di caratteri, come nel mio esempio semplificato. Se m_size <= 16
, allora inserirò tutti i dati m_sso
, quindi conosco già la capacità e non ho bisogno del puntatore ai dati. Se m_size > 16
non ho bisogno m_sso
. Non c'è assolutamente alcuna sovrapposizione dove ho bisogno di tutti loro. Una soluzione più intelligente che non spreca spazio avrebbe un aspetto un po 'più simile a questo (non testato, solo a scopo di esempio):
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;
};
};
Suppongo che la maggior parte delle implementazioni sia più simile a questa.
std::string
implementata" e un'altra chiede "cosa significa SSO", devi essere assolutamente pazzo per considerarli come la stessa domanda