Escuché a algunas personas expresar preocupaciones sobre el operador "+" en std :: string y varias soluciones para acelerar la concatenación. ¿Alguno de estos es realmente necesario? Si es así, ¿cuál es la mejor manera de concatenar cadenas en C ++?
c++
performance
string
concatenation
burlarse
fuente
fuente
libstdc++
hace esto, por ejemplo . Entonces, cuando se llama a operator + con temporales, puede lograr un rendimiento casi tan bueno, tal vez un argumento a favor de no cumplirlo, en aras de la legibilidad, a menos que uno tenga puntos de referencia que muestren que es un cuello de botella. Sin embargo, una variada estandarizadaappend()
sería óptima y legible ...Respuestas:
El trabajo adicional probablemente no valga la pena, a menos que realmente necesite eficiencia. Probablemente tendrá una eficiencia mucho mejor simplemente usando el operador + = en su lugar.
Ahora, después de ese descargo de responsabilidad, responderé a su pregunta real ...
La eficiencia de la clase de cadena STL depende de la implementación de STL que esté utilizando.
Puede garantizar la eficiencia y tener un mayor control si realiza la concatenación manualmente mediante las funciones integradas de c.
Por qué operator + no es eficiente:
Eche un vistazo a esta interfaz:
Puede ver que se devuelve un nuevo objeto después de cada +. Eso significa que se utiliza un búfer nuevo cada vez. Si está haciendo un montón de operaciones adicionales, no es eficiente.
Por qué puede hacerlo más eficiente:
Consideraciones para la implementación:
Estructura de datos de la cuerda:
Si necesita concatenaciones realmente rápidas, considere usar una estructura de datos de cuerdas .
fuente
Reserve su espacio final antes, luego use el método de adición con un búfer. Por ejemplo, supongamos que espera que la longitud de la cadena final sea de 1 millón de caracteres:
fuente
Yo no me preocuparía por eso. Si lo hace en un bucle, las cadenas siempre preasignarán memoria para minimizar las reasignaciones; solo úselas
operator+=
en ese caso. Y si lo haces manualmente, algo como esto o másLuego está creando temporales, incluso si el compilador pudiera eliminar algunas copias del valor de retorno. Esto se debe a que en un llamado sucesivamente
operator+
no se sabe si el parámetro de referencia hace referencia a un objeto con nombre o un retorno temporal de unaoperator+
subinvocación. Preferiría no preocuparme por eso antes de no haber perfilado primero. Pero tomemos un ejemplo para demostrarlo. Primero introducimos paréntesis para aclarar el enlace. Pongo los argumentos directamente después de la declaración de función que se usa para mayor claridad. Debajo de eso, muestro cuál es la expresión resultante:Ahora, en esa adición,
tmp1
es lo que devolvió la primera llamada al operador + con los argumentos mostrados. Suponemos que el compilador es realmente inteligente y optimiza la copia del valor de retorno. Así que terminamos con una nueva cadena que contiene la concatenación dea
y" : "
. Ahora, esto sucede:Compare eso con lo siguiente:
¡Está usando la misma función para una cadena temporal y para una cadena con nombre! Entonces el compilador tiene que copiar el argumento en una nueva cadena y agregarlo y devolverlo desde el cuerpo de
operator+
. No puede tomar la memoria de un temporal y agregarlo. Cuanto más grande sea la expresión, más copias de cadenas deberán realizarse.Siguiente Visual Studio y GCC admitirán la semántica de movimiento de c ++ 1x (complementando la semántica de copia ) y las referencias rvalue como una adición experimental. Eso permite averiguar si el parámetro hace referencia a un temporal o no. Esto hará que las adiciones sean increíblemente rápidas, ya que todo lo anterior terminará en una "tubería de adición" sin copias.
Si resulta ser un cuello de botella, aún puede hacerlo
Las
append
llamadas añaden el argumento ay*this
luego devuelven una referencia a sí mismas. Por lo tanto, no se realiza ninguna copia de temporales allí. O alternativamente,operator+=
se puede usar, pero necesitaría paréntesis feos para arreglar la precedencia.fuente
libstdc++
deoperator+(string const& lhs, string&& rhs)
hacereturn std::move(rhs.insert(0, lhs))
. Entonces, si ambos son temporales,operator+(string&& lhs, string&& rhs)
silhs
tiene suficiente capacidad disponible, lo hará directamenteappend()
. Donde creo que esto corre el riesgo de ser más lento de lo queoperator+=
es silhs
no tiene suficiente capacidad, ya que luego retrocederhs.insert(0, lhs)
, lo que no solo debe extender el búfer y agregar los nuevos contenidos comoappend()
, sino que también debe desplazarse a lo largo del contenido original derhs
right.operator+=
es queoperator+
aún debe devolver un valor, por lo que tiene quemove()
depender del operando al que se anexó. Aún así, supongo que es una sobrecarga bastante menor (copiar un par de punteros / tamaños) en comparación con la copia profunda de toda la cadena, ¡así que es bueno!Para la mayoría de las aplicaciones, simplemente no importa. Simplemente escriba su código, felizmente inconsciente de cómo funciona exactamente el operador +, y solo tome el asunto en sus propias manos si se convierte en un cuello de botella aparente.
fuente
A diferencia de .NET System.Strings, std :: strings de C ++ son mutables y, por lo tanto, se pueden construir mediante una simple concatenación tan rápido como con otros métodos.
fuente
operator+
no tiene que devolver una nueva cadena. Los implementadores pueden devolver uno de sus operandos, modificado, si ese operando fue pasado por la referencia rvalue.libstdc++
hace esto, por ejemplo . Por lo tanto, cuando se llamaoperator+
con provisionales, puede lograr el mismo o casi el mismo rendimiento, lo que podría ser otro argumento a favor de no hacerlo, a menos que se tengan puntos de referencia que muestren que representa un cuello de botella.¿quizás std :: stringstream en su lugar?
Pero estoy de acuerdo con la opinión de que probablemente debería mantenerlo mantenible y comprensible y luego perfilarlo para ver si realmente está teniendo problemas.
fuente
En Imperfect C ++ , Matthew Wilson presenta un concatenador dinámico de cadenas que calcula previamente la longitud de la cadena final para tener solo una asignación antes de concatenar todas las partes. También podemos implementar un concatenador estático jugando con plantillas de expresión .
Ese tipo de idea se ha implementado en la implementación de STLport std :: string, que no se ajusta al estándar debido a este truco preciso.
fuente
Glib::ustring::compose()
de los enlaces glibmm a GLib hace eso: estima yreserve()
s la longitud final basada en la cadena de formato proporcionada y los varargs, luegoappend()
s cada uno (o su reemplazo formateado) en un bucle. Espero que esta sea una forma bastante común de trabajar.std::string
operator+
asigna una nueva cadena y copia las dos cadenas de operandos cada vez. repite muchas veces y se vuelve caro, O (n).std::string
append
y,operator+=
por otro lado, aumente la capacidad en un 50% cada vez que la cuerda necesite crecer. Lo que reduce significativamente el número de asignaciones de memoria y operaciones de copia, O (log n).fuente
operator+
dónde se pasa uno o ambos argumentos mediante la referencia rvalue pueden evitar la asignación de una nueva cadena por completo concatenando en el búfer existente de uno de los operandos (aunque es posible que tengan que reasignarlo si no tiene capacidad suficiente).Para cuerdas pequeñas, no importa. Si tiene cadenas grandes, será mejor que las almacene como están en vector o en alguna otra colección como partes. Y adapte su algoritmo para trabajar con ese conjunto de datos en lugar de una cadena grande.
Prefiero std :: ostringstream para la concatenación compleja.
fuente
Como ocurre con la mayoría de las cosas, es más fácil no hacer algo que hacerlo.
Si desea generar cadenas grandes en una GUI, puede ser que lo que sea que esté generando pueda manejar las cadenas en partes mejor que como una cadena grande (por ejemplo, concatenando texto en un editor de texto; por lo general, mantienen las líneas separadas estructuras).
Si desea exportar a un archivo, transmita los datos en lugar de crear una cadena grande y generarla.
Nunca he encontrado la necesidad de hacer que la concatenación sea más rápida si eliminé la concatenación innecesaria del código lento.
fuente
Probablemente el mejor rendimiento si preasigna (reserva) espacio en la cadena resultante.
Uso:
fuente
Una matriz simple de caracteres, encapsulada en una clase que realiza un seguimiento del tamaño de la matriz y el número de bytes asignados es la más rápida.
El truco consiste en hacer solo una gran asignación al principio.
a
https://github.com/pedro-vicente/table-string
Benchmarks
Para Visual Studio 2015, compilación de depuración x86, mejora sustancial sobre C ++ std :: string.
fuente
std::string
. No están pidiendo una clase de cadena alternativa.Puede probar este con reservas de memoria para cada elemento:
fuente