c++ std::ostringstream vs std::string::append

1 minuto de lectura

avatar de usuario
NickSoft

En todos los ejemplos que usan algún tipo de almacenamiento en búfer, veo que usan flujo en lugar de cadena. ¿En qué se diferencian std::ostringstream y

Una diferencia que sé es que puede generar diferentes tipos en el flujo de salida (como un número entero) en lugar de los tipos limitados que acepta string::append.

Aquí hay un ejemplo:

std::ostringstream os;
os << "Content-Type: " << contentType << ";charset=" << charset << "\r\n";
std::string header = os.str();

contra

std::string header("Content-Type: ");
header.append(contentType);
header.append(";charset=");
header.append(charset);
header.append("\r\n");

Obviamente, usar stream es más corto, pero creo que append devuelve la referencia a la cadena para que pueda escribirse así:

std::string header("Content-Type: ");
header.append(contentType)
  .append(";charset=")
  .append(charset)
  .append("\r\n");

Y con el flujo de salida puedes hacer:

std::string content;
...
os << "Content-Length: " << content.length() << "\r\n";

Pero, ¿qué pasa con el uso de la memoria y la velocidad? Especialmente cuando se usa en un bucle grande.

Actualizar:

Para ser mas claro la pregunta es: ¿Cuál debo usar y por qué? ¿Hay situaciones en las que se prefiere uno u otro? Para el rendimiento y la memoria … bueno, creo que el punto de referencia es la única forma, ya que cada implementación podría ser diferente.

Actualización 2:

Bueno, no tengo una idea clara de qué debo usar de las respuestas, lo que significa que cualquiera de ellos hará el trabajo, más el vector. Cubbi Hizo un buen punto de referencia con la adición de Dietmar Kühl de que la mayor diferencia es la construcción de esos objetos. Si está buscando una respuesta, también debe verificarla. Esperaré un poco más por otras respuestas (busque la actualización anterior) y si no obtengo una, creo que aceptaré la respuesta de Tolga porque su sugerencia de usar el vector ya se hizo antes, lo que significa que el vector debería consumir menos recursos.

  • Offtopic: también debe buscar una función rápida para convertir enteros en cadenas/caracteres. sprintf/itoa no funciona bien para hacer una conversión de cadena simple de entero a decimal para Content-Length.

    – Etéreo solo

    7 noviembre 2013 a las 20:20


  • sprintf podría ser lento debido a las opciones de formato, pero ¿por qué cree que itoa es lento?

    – NickSoft

    8 de noviembre de 2013 a las 5:29

  • No debería haber escrito itoa allí. Quise decir que itoa no debería ser una opción porque no es estándar. Pero recuerdo compararlo con estos: gist.github.com/anonymous/7179097

    – Etéreo solo

    8 de noviembre de 2013 a las 15:07


avatar de usuario
cubbi

construyendo un objeto de flujo es una operación significativamente más compleja que la construcción de un objeto de cadena, porque tiene que contener (y, por lo tanto, construir) su std::locale miembro, entre otras cosas necesarias para mantener el estado (pero la configuración regional es, con mucho, la más pesada).

Agregar es similar: ambos mantienen una matriz contigua de caracteres, ambos asignan más cuando se excede la capacidad. Las únicas diferencias en las que puedo pensar es que cuando se agrega a una secuencia, hay una llamada de función de miembro virtual por desbordamiento (además de la asignación/copia de memoria, que domina el manejo de desbordamiento de todos modos), y operator<< tiene que hacer algunas comprobaciones adicionales del estado de la transmisión.

Además, tenga en cuenta que está llamando a str(), que copia la cadena completa una vez más, por lo que, según lo que su código está escrito para hacer, el ejemplo de flujo hace más y debería ser más lento.

Probemos:

#include <sstream>
#include <string>
#include <numeric>

volatile unsigned int sink;
std::string contentType(50, ' ');
std::string charset(50, ' ');
int main()
{
 for(long n = 0; n < 10000000; ++n)
 {
#ifdef TEST_STREAM    
    std::ostringstream os;
    os << "Content-Type: " << contentType << ";charset=" << charset << "\r\n";
    std::string header = os.str();
#endif
#ifdef TEST_STRING
    std::string header("Content-Type: ");
    header.append(contentType);
    header.append(";charset=");
    header.append(charset);
    header.append("\r\n");
#endif
    sink += std::accumulate(header.begin(), header.end(), 0);
 }
}

ese es 10 millones repeticiones

En mi Linux, obtengo

                   stream         string
g++ 4.8          7.9 seconds      4.4 seconds
clang++/libc++  11.3 seconds      3.3 seconds

entonces, para este caso de uso, en estas dos implementaciones, las cadenas parecen funcionar más rápido, pero obviamente ambas formas tienen mucho que mejorar (reservar () la cadena, mover la construcción del flujo fuera del ciclo, usar un flujo que no requiere copiar para acceder a su búfer, etc.)

  • Te estás olvidando de manejar algo como std::ios_base::width

    – Eslava

    7 de noviembre de 2013 a las 20:07

  • @Slava editado en una mención de honor como carga útil adicional para la construcción de secuencias: el operador de cadena

    – Cubbi

    7 de noviembre de 2013 a las 20:16

  • Cambiar ligeramente la configuración para construir la transmisión fuera del bucle y simplemente restablecerla (os.str("")) cambia los números de maneras interesantes: la transmisión ahora es más rápida en gcc pero más lenta en clang. Obtengo gcc/string=4.5s, gcc/stream=2.5s, clang/string=2.25s, clang/stream=4.1s: bien cruzado;)

    – Dietmar Kühl

    7 de noviembre de 2013 a las 20:25

  • entonces, a menos que esté construyendo la transmisión cada vez que en realidad es comparable al uso de una cadena.

    – NickSoft

    8 de noviembre de 2013 a las 5:24

avatar de usuario
etéreo solo

std::ostringstream no se almacena necesariamente como una matriz secuencial de caracteres en la memoria. En realidad, necesitaría tener una matriz continua de caracteres al enviar esos encabezados HTTP y eso podría copiar/modificar el búfer interno para que sea secuencial.

std::string usando apropiado std::string::reserve no tiene ninguna razón para actuar más lento que std::ostringstream en esta situación.

Sin embargo, std::ostringstream es probablemente más rápido para agregar si no tiene idea del tamaño que tiene que reservar. Si utiliza std::string y su cadena crece, eventualmente requiere reasignación y copia de todo el búfer. seria mejor usar uno std::ostringstream::str() para hacer que los datos sean secuenciales a la vez en comparación con las reasignaciones múltiples que ocurrirían de otra manera.

PS Pre-C++11 std::string tampoco se requiere que sea secuencial, mientras que casi todas las bibliotecas lo implementan como secuencial. Podrías arriesgarte o usar std::vector<char> en cambio. Necesitaría usar lo siguiente para agregar:

char str[] = ";charset=";
vector.insert(vector.end(), str, str + sizeof(str) - 1);

std::vector<char> Sería mejor para el rendimiento porque probablemente sea más barato de construir, pero probablemente no sea de importancia en comparación con std::string y el tiempo real que tardan en construirse. Hice algo similar a lo que estás intentando y fui con std::vector<char> antes de. Puramente por razones lógicas; vector parecía ajustarse mejor al trabajo. En realidad, no desea manipulaciones de cadenas o algo así. Además, los puntos de referencia que hice más tarde demostraron que funcionaba mejor o tal vez fue solo porque no implementé las operaciones lo suficientemente bien con std::string.

Al elegir, el contenedor que tiene requisitos para sus necesidades y características adicionales mínimas generalmente hace el mejor trabajo.

  • aumentar los amortiguadores sobre la marcha es contradictoriamente barato.

    – jthill

    7 de noviembre de 2013 a las 19:48

  • Uso de la reserva adecuada Estoy de acuerdo, de lo contrario implica una reasignación continua de memoria y, por lo tanto, un menor rendimiento. Y a pesar del hecho ostringstream no lo almacena secuencialmente (por razones de rendimiento) no significa que no pueda recuperarlo en un búfer continuo con str().c_str().

    – Havenard

    7 de noviembre de 2013 a las 19:51


  • @NickSoft Dado que el vector es secuencial, puede acceder a su búfer accediendo a su primer elemento: char const* data = &vector[0];

    – Etéreo solo

    8 de noviembre de 2013 a las 14:53


  • @NickSoft No creo que ningún encabezado supere los 2 KB, lo que parece ser un buen valor para la reserva. Incluso si tiene 1 millón de clientes conectados, solo usaría 2 GB de RAM, lo que no será nada en comparación con lo que su servidor de base de datos necesitará para funcionar decentemente con esa cantidad de tráfico, suponiendo que solo el 5~10% de los usuarios reales estarán conectados simultáneamente (de por supuesto, esta es una suposición cruda y las operaciones de la base de datos pueden no existir en absoluto si no son muy baratas). Incluso puede registrar estadísticas y hacer cálculos una vez al día para encontrar el tamaño adecuado para reservar dinámicamente.

    – Etéreo solo

    8 de noviembre de 2013 a las 15:24

  • @NickSoft es.cppreference.com/w/cpp/container/vector : Los elementos se almacenan de forma contigua, lo que significa que se puede acceder a los elementos no solo a través de iteradores, sino también mediante compensaciones en punteros regulares a elementos. Esto significa que un puntero a un elemento de un vector se puede pasar a cualquier función que espere un puntero a un elemento de una matriz. (Por cierto, sugiero usar cppreference.com para buscar cosas)

    – Etéreo solo

    8 de noviembre de 2013 a las 18:21

Con stream puedes tener tu clase Myclass anular el << operación para que puedas escribir

MyClass x;
ostringstream y;
y << x;

Para agregar, debe tener un método ToString (o algo similar), ya que no puede anular la función de agregar cadena.

Para algunas piezas de código, use lo que le resulte más cómodo. Use stream para proyectos más grandes donde es útil poder simplemente transmitir un objeto.

  • pero como señaló Dieter Lücking, podría usar + para agregar cadenas. Puede anular fácilmente el operador +.

    – NickSoft

    7 de noviembre de 2013 a las 20:01

  • Cierto, pero no la función de agregar. Si anula el operador +, puede tener problemas por no anular todas las órdenes, o cuando el compilador decide evaluar alguna otra operación primero. Recomiendo no anular el operador +, a menos que su clase sea un valor escalar o vectorial.

    – Sorín

    7 de noviembre de 2013 a las 20:06

avatar de usuario
Eslava

Si le preocupa la velocidad, debe perfilar y/o probar. En teoria std::string::append no debe ser más lento, ya que es más simple (la transmisión tiene que lidiar con la configuración regional, el formato diferente y ser más genérica). Pero cuán rápida es realmente una solución u otra solo puede darse cuenta mediante la prueba.

¿Ha sido útil esta solución?