Antecedentes / Resumen
Las operaciones en variables automáticas ("de la pila", que son variables que crea sin llamar malloc
/ new
) son generalmente mucho más rápidas que las que involucran la tienda libre ("el montón", que son variables que se crean usando new
). Sin embargo, el tamaño de las matrices automáticas se fija en el momento de la compilación, pero el tamaño de las matrices de la tienda gratuita no lo es. Además, el tamaño de la pila es limitado (generalmente unos pocos MiB), mientras que la tienda gratuita solo está limitada por la memoria de su sistema.
SSO es la optimización de cadenas cortas / pequeñas. A std::string
generalmente almacena la cadena como un puntero a la tienda libre ("el montón"), que proporciona características de rendimiento similares a las de llamar new char [size]
. Esto evita un desbordamiento de la pila para cadenas muy grandes, pero puede ser más lento, especialmente con operaciones de copia. Como optimización, muchas implementaciones de std::string
crear una pequeña matriz automática, algo así char [20]
. Si tiene una cadena de 20 caracteres o menos (dado este ejemplo, el tamaño real varía), la almacena directamente en esa matriz. Esto evita la necesidad de llamar new
, lo que acelera un poco las cosas.
EDITAR:
No esperaba que esta respuesta fuera tan popular, pero dado que lo es, permítanme dar una implementación más realista, con la advertencia de que nunca he leído ninguna implementación de SSO "en la naturaleza".
Detalles de implementacion
Como mínimo, a std::string
necesita almacenar la siguiente información:
- El tamaño
- La capacidad
- La ubicación de los datos.
El tamaño podría almacenarse como un std::string::size_type
o como un puntero al final. La única diferencia es si desea restar dos punteros cuando el usuario llama size
o agregar size_type
un puntero cuando el usuario llama end
. La capacidad se puede almacenar de cualquier manera también.
No pagas por lo que no usas.
Primero, considere la implementación ingenua basada en lo que describí anteriormente:
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;
};
Para un sistema de 64 bits, eso generalmente significa que std::string
tiene 24 bytes de 'sobrecarga' por cadena, más otros 16 para el búfer SSO (16 elegidos aquí en lugar de 20 debido a los requisitos de relleno). Realmente no tendría sentido almacenar esos tres miembros de datos más una matriz local de caracteres, como en mi ejemplo simplificado. Si m_size <= 16
, entonces pondré todos los datos m_sso
, así que ya sé la capacidad y no necesito el puntero a los datos. Si m_size > 16
, entonces no lo necesito m_sso
. No hay absolutamente ninguna superposición donde los necesito a todos. Una solución más inteligente que no desperdicia espacio se vería algo más como esto (no probado, solo con fines de ejemplo):
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;
};
};
Supongo que la mayoría de las implementaciones se parecen más a esto.
std::string
implementa", y otra pregunta: "¿Qué SSO media", que tiene que ser absolutamente loco para considerarlos a ser la misma pregunta