¿Por qué la implementación de std :: string de libc ++ ocupa una memoria 3x como libstdc ++?

Considere el siguiente progtwig de prueba:

#include  #include  #include  int main() { std::cout << sizeof(std::string("hi")) << " "; std::string a[10]; std::cout << sizeof(a) << " "; std::vector v(10); std::cout << sizeof(v) + sizeof(std::string) * v.capacity() << "\n"; } 

La salida para libstdc++ y libc++ respectivamente son:

 8 80 104 24 240 264 

Como puede ver, libc++ toma 3 veces más memoria para un progtwig simple. ¿Cómo difiere la implementación que causa esta disparidad de memoria? ¿Debo preocuparme y cómo lo soluciono?

Aquí hay un progtwig corto para ayudarlo a explorar los dos tipos de uso de memoria de std::string : stack y heap.

 #include  #include  #include  #include  std::size_t allocated = 0; void* operator new (size_t sz) { void* p = std::malloc(sz); allocated += sz; return p; } void operator delete(void* p) noexcept { return std::free(p); } int main() { allocated = 0; std::string s("hi"); std::printf("stack space = %zu, heap space = %zu, capacity = %zu\n", sizeof(s), allocated, s.capacity()); } 

Usando http://melpon.org/wandbox/ es fácil obtener resultados para diferentes combinaciones comstackdor / lib, por ejemplo:

gcc 4.9.1:

 stack space = 8, heap space = 27, capacity = 2 

gcc 5.0.0:

 stack space = 32, heap space = 0, capacity = 15 

clang / libc ++:

 stack space = 24, heap space = 0, capacity = 22 

VS-2015:

 stack space = 32, heap space = 0, capacity = 15 

(la última línea es de http://webcompiler.cloudapp.net )

El resultado anterior también muestra capacity , que es una medida de cuántas cadenas puede contener la cadena antes de que tenga que asignar un nuevo búfer más grande del montón. Para las implementaciones gcc-5.0, libc ++ y VS-2015, esta es una medida del búfer de cadena corta . Es decir, el búfer de tamaño asignado en la stack para contener cadenas cortas, evitando así la asignación de montón más costosa.

Parece que la implementación de libc ++ tiene el menor (uso de la stack) de las implementaciones de cadenas cortas, y aún contiene el más grande de los búferes de cadenas cortas. Y si cuenta el uso total de la memoria (stack + heap), libc ++ tiene el uso de memoria total más pequeño para esta cadena de 2 caracteres entre las 4 implementaciones.

Cabe señalar que todas estas medidas se tomaron en plataformas de 64 bits. En 32 bits, el uso de la stack de libc ++ bajará a 12, y el pequeño búfer de cadena bajará a 10. No conozco el comportamiento de las otras implementaciones en plataformas de 32 bits, pero puede usar el código anterior para averiguar .

No debe preocuparse, los implementadores de bibliotecas estándar saben lo que están haciendo.

Usando el último código del tronco de subversión de GCC, libstdc ++ da estos números:

 32 320 344 

Esto se debe a que hace unas semanas cambié la implementación predeterminada de std::string para usar la optimización de cadena pequeña (con espacio para 15 caracteres) en lugar de la implementación de copia en escritura con la que probaste.

Resumen: solo parece que libstdc++ usa un char* . De hecho, asigna más memoria.

Por lo tanto, no debe preocuparse porque la implementación de libc++ Clang sea ineficiente en la memoria.

De la documentación de libstdc ++ (en Descripción detallada ):

 A string looks like this: [_Rep] _M_length [basic_string] _M_capacity _M_dataplus _M_refcount _M_p ----------------> unnamed array of char_type 

Donde _M_p apunta al primer caracter de la cadena, y lo convierte en un puntero a _Rep y resta 1 para obtener un puntero al encabezado.

Este enfoque tiene la enorme ventaja de que un objeto cadena requiere solo una asignación. Toda la fealdad está confinada dentro de un solo par de funciones en línea, que comstackn en una única instrucción add: _Rep :: _ M_data (), y string :: _ M_rep (); y la función de asignación que obtiene un bloque de bytes sin procesar y con suficiente espacio y construye un objeto _Rep en el frente.

La razón por la que desea que _M_data apunte a la matriz de caracteres y no a la _Rep es para que el depurador pueda ver el contenido de la cadena. (Probablemente deberíamos agregar un miembro que no esté en línea para obtener el _Rep para que lo use el depurador, para que los usuarios puedan verificar la longitud real de la cadena).

Por lo tanto, se ve como un char* pero eso es engañoso en términos de uso de la memoria.

Anteriormente libstdc++ básicamente usaba este diseño:

  struct _Rep_base { size_type _M_length; size_type _M_capacity; _Atomic_word _M_refcount; }; 

Eso está más cerca de los resultados de libc++ .

libc++ usa “optimización de cadena corta”. El diseño exacto depende de si se ha definido _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT . Si está definido, el puntero de datos se alineará con la palabra si la cadena es corta. Para más detalles, vea el código fuente .

La optimización de cadena corta evita las asignaciones de montón, por lo que también parece más costosa que la implementación libstdc++ si solo se tienen en cuenta las partes asignadas en la stack. sizeof(std::string) solo muestra el uso de la stack, no el uso general de la memoria (stack + stack).

No he comprobado las implementaciones reales en el código fuente, pero recuerdo haber verificado esto cuando estaba trabajando en mi biblioteca de cadenas C ++. Una implementación de cadena de 24 bytes es típica. Si la longitud de la cadena es menor o igual a 16 bytes, en lugar de malloclar del montón, copia la cadena en el búfer interno de 16 bytes de tamaño. De lo contrario, mallocs y almacena la dirección de la memoria, etc. Este pequeño búfer en realidad ayuda en términos de rendimiento del tiempo de ejecución.

Para algunos comstackdores, hay una opción para desactivar el búfer interno.