Significado del acrónimo SSO en el contexto de std :: string


155

En una pregunta de C ++ sobre optimización y estilo de código , varias respuestas se referían a "SSO" en el contexto de la optimización de copias de std::string. ¿Qué significa SSO en ese contexto?

Claramente no es "inicio de sesión único". ¿"Optimización de cadena compartida", tal vez?


57
Eso es solo un duplicado de la misma manera que "lo que es 2 + 2" es un duplicado de "lo que es el resultado de 200/50". La respuesta es la misma. La pregunta es completamente diferente. "Cerrar como duplicado" está destinado a usarse cuando varias personas hacen la misma * pregunta. Cuando una persona se pregunta "cómo se std::stringimplementa", y otra pregunta: "¿Qué SSO media", que tiene que ser absolutamente loco para considerarlos a ser la misma pregunta
JALF

1
@jalf: Si hay un Q + A existente que abarca exactamente el alcance de esta pregunta, lo consideraría un duplicado (no estoy diciendo que el OP debería haber buscado esto él mismo, simplemente que cualquier respuesta aquí cubrirá el terreno que es ya ha sido cubierto.)
Oliver Charlesworth

47
De hecho, le está diciendo al OP que "su pregunta es incorrecta. Pero necesitaba saber la respuesta para saber lo que debería haber preguntado". Buena manera de apagar a la gente SO. También hace innecesariamente difícil encontrar la información que necesita. Si las personas no hacen preguntas (y el cierre es efectivamente decir "esta pregunta no debería haberse hecho"), entonces no habría forma posible de que las personas que aún no saben la respuesta obtengan la respuesta a esta pregunta
jalf

77
@jalf: en absoluto. OMI, "votar para cerrar" no implica "mala pregunta". Yo uso downvotes para eso. Considero que es un duplicado en el sentido de que todas las innumerables preguntas (i = i ++, etc.) cuya respuesta es "comportamiento indefinido" son duplicados entre sí. En una nota diferente, ¿por qué nadie ha respondido la pregunta si no es un duplicado?
Oliver Charlesworth el

55
@jalf: Estoy de acuerdo con Oli, la pregunta no es un duplicado, pero la respuesta sería, por lo tanto, redirigir a otra pregunta donde las respuestas ya parecen apropiadas. Las preguntas cerradas como duplicados no desaparecen, sino que actúan como indicadores hacia otra pregunta donde se encuentra la respuesta. La próxima persona que busque SSO terminará aquí, seguirá la redirección y encontrará su respuesta.
Matthieu M.

Respuestas:


213

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::stringgeneralmente 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::stringcrear 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::stringnecesita 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_typeo como un puntero al final. La única diferencia es si desea restar dos punteros cuando el usuario llama sizeo agregar size_typeun 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::stringtiene 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.


77
Aquí hay una buena explicación de algunas implementaciones reales: stackoverflow.com/a/28003328/203044
BillT

¿SSO es realmente práctico cuando la mayoría de los desarrolladores pasan std :: string usando referencias constantes?
Gupta

1
SSO tiene dos beneficios más allá de hacer la copia más barata. La primera es que si el tamaño de su cadena se ajusta al tamaño del búfer pequeño, no necesita asignarlo en la construcción inicial. El segundo es que cuando una función acepta un std::string const &, obtener los datos es una indirecta de memoria única, porque los datos se almacenan en la ubicación de la referencia. Si no hubiera una optimización de cadena pequeña, el acceso a los datos requeriría dos indirecciones de memoria (primero para cargar la referencia a la cadena y leer su contenido, luego la segunda para leer el contenido del puntero de datos en la cadena).
David Stone

34

SSO es la abreviatura de "Small String Optimization", una técnica en la que las pequeñas cadenas están incrustadas en el cuerpo de la clase de cadena en lugar de utilizar un búfer asignado por separado.


15

Como ya se explicó en las otras respuestas, SSO significa optimización de cadena pequeña / corta . La motivación detrás de esta optimización es la evidencia innegable de que las aplicaciones en general manejan cadenas mucho más cortas que cadenas más largas.

Como explicó David Stone en su respuesta anterior , la std::stringclase usa un búfer interno para almacenar contenidos de una longitud determinada, y esto elimina la necesidad de asignar memoria de forma dinámica. Esto hace que el código sea más eficiente y rápido .

Esta otra respuesta relacionada muestra claramente que el tamaño del búfer interno depende de la std::stringimplementación, que varía de una plataforma a otra (ver los resultados de referencia a continuación).

Puntos de referencia

Aquí hay un pequeño programa que compara la operación de copia de muchas cadenas con la misma longitud. Comienza a imprimir el tiempo para copiar 10 millones de cadenas con longitud = 1. Luego se repite con cadenas de longitud = 2. Continúa hasta que la longitud sea 50.

#include <string>
#include <iostream>
#include <vector>
#include <chrono>

static const char CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static const int ARRAY_SIZE = sizeof(CHARS) - 1;

static const int BENCHMARK_SIZE = 10000000;
static const int MAX_STRING_LENGTH = 50;

using time_point = std::chrono::high_resolution_clock::time_point;

void benchmark(std::vector<std::string>& list) {
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();

    // force a copy of each string in the loop iteration
    for (const auto s : list) {
        std::cout << s;
    }

    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    std::cerr << list[0].length() << ',' << duration << '\n';
}

void addRandomString(std::vector<std::string>& list, const int length) {
    std::string s(length, 0);
    for (int i = 0; i < length; ++i) {
        s[i] = CHARS[rand() % ARRAY_SIZE];
    }
    list.push_back(s);
}

int main() {
    std::cerr << "length,time\n";

    for (int length = 1; length <= MAX_STRING_LENGTH; length++) {
        std::vector<std::string> list;
        for (int i = 0; i < BENCHMARK_SIZE; i++) {
            addRandomString(list, length);
        }
        benchmark(list);
    }

    return 0;
}

Si desea ejecutar este programa, debe hacerlo de ./a.out > /dev/nullmanera que no se cuente el tiempo para imprimir las cadenas. Los números que importan se imprimen enstderr que aparezcan en la consola.

He creado gráficos con la salida de mis máquinas MacBook y Ubuntu. Tenga en cuenta que hay un gran salto en el tiempo para copiar las cadenas cuando la longitud alcanza un punto determinado. Ese es el momento en que las cadenas ya no caben en el búfer interno y se debe utilizar la asignación de memoria.

Tenga en cuenta también que en la máquina Linux, el salto ocurre cuando la longitud de la cadena alcanza 16. En el macbook, el salto ocurre cuando la longitud alcanza 23. Esto confirma que SSO depende de la implementación de la plataforma.

Ubuntu SSO benchmark en Ubuntu

Macbook Pro SSO benchmark en Macbook Pro

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.