¿Alguien puede vencer el rendimiento de mi entero al código std :: string, vinculado a continuación?
Ya hay varias preguntas que explican cómo convertir un número entero en a std::string
en C ++, como este , pero ninguna de las soluciones proporcionadas es eficiente.
Aquí hay un código listo para compilar para algunos métodos comunes contra los que competir:
- La "forma C ++", usando stringstream: http://ideone.com/jh3Sa
- sprintf, que los SO-ers suelen recomendar a los usuarios conscientes del rendimiento: http://ideone.com/82kwR
Contrariamente a la creencia popular , boost::lexical_cast
tiene su propia implementación ( documento técnico ) y no utiliza stringstream
operadores de inserción numérica. Realmente me gustaría ver su rendimiento comparado, porque esta otra pregunta sugiere que es miserable .
Y mi propia contribución, que es competitiva en las computadoras de escritorio, y demuestra un enfoque que también funciona a toda velocidad en los sistemas integrados, a diferencia de los algoritmos que dependen del módulo entero:
- Los algoritmos de Ben: http://ideone.com/SsEUW
Si desea usar ese código, lo pondré a disposición bajo una licencia BSD simplificada (uso comercial permitido, se requiere atribución). Solo pregunta.
Finalmente, la función ltoa
no es estándar pero está ampliamente disponible.
- Versión ltoa, para cualquiera que tenga un compilador que lo proporcione (ideone no): http://ideone.com/T5Wim
Publicaré mis medidas de rendimiento como respuesta en breve.
Reglas para algoritmos
- Proporcione código para una conversión de enteros con signo y sin signo de al menos 32 bits a decimal.
- Producir salida como a
std::string
. - No hay trucos que sean incompatibles con subprocesos y señales (por ejemplo, buffers estáticos).
- Puede asumir un conjunto de caracteres ASCII.
- Asegúrese de probar su código en
INT_MIN
una máquina complementaria de dos donde el valor absoluto no sea representable. - Idealmente, la salida debería ser carácter por carácter idéntica a la versión canónica de C ++ que utiliza
stringstream
, http://ideone.com/jh3Sa , pero cualquier cosa que sea claramente comprensible ya que el número correcto también está bien. - NUEVO : aunque puede usar cualquier compilador y opciones de optimizador (excepto completamente deshabilitado) que desee para la comparación, el código también debe compilarse y dar resultados correctos en al menos VC ++ 2010 y g ++.
Discusión esperada
Además de mejores algoritmos, también me gustaría obtener algunos puntos de referencia en varias plataformas y compiladores diferentes (usemos el rendimiento de MB / s como nuestra unidad de medida estándar). Creo que el código para mi algoritmo (sé que el sprintf
punto de referencia toma algunos accesos directos, ahora corregidos) es un comportamiento bien definido por el estándar, al menos bajo el supuesto ASCII, pero si ve algún comportamiento indefinido o entradas para las cuales el resultado no es válido, indícalo.
Conclusiones:
Los diferentes algoritmos funcionan para g ++ y VC2010, probablemente debido a las diferentes implementaciones de std::string
cada uno. VC2010 claramente hace un mejor trabajo con NRVO, deshacerse del retorno por valor solo ayudó en gcc.
Se encontró un código que supera sprintf
en un orden de magnitud. ostringstream
se queda atrás por un factor de 50 y más.
El ganador del desafío es user434507, que produce un código que ejecuta el 350% de mi velocidad en gcc. Otras entradas están cerradas debido a los caprichos de la comunidad SO.
Los campeones de velocidad actuales (¿finales?) Son:
- Para gcc: user434507, a 8 veces más rápido que
sprintf
: http://ideone.com/0uhhX - Para Visual C ++: Timo, 15 veces más rápido que
sprintf
: http://ideone.com/VpKO3
fuente
Respuestas:
Esto explotará en los sistemas que no permiten los accesos de memoria no alineados (en cuyo caso, la primera asignación no alineada a través de
*(short*)
causaría un defecto), pero de lo contrario debería funcionar muy bien.Una cosa importante que hacer es minimizar el uso de
std::string
. (Irónico, lo sé). En Visual Studio, por ejemplo, la mayoría de las llamadas a los métodos de std :: string no están en línea, incluso si especifica / Ob2 en las opciones del compilador. Por lo tanto, incluso algo tan trivial como una llamadastd::string::clear()
, que puede esperar que sea muy rápido, puede tomar 100 tics al vincular CRT como una biblioteca estática, y hasta 300 tics al vincular como una DLL.Por la misma razón, regresar por referencia es mejor porque evita una asignación, un constructor y un destructor.
fuente
sprintf
. Y con VC ++ 2010, obtiene aproximadamente 50 MB / s, aproximadamente el doble de la velocidad de sprintf.sprintf
implementación, ya lo mencioné en mi pregunta, pero creo que el código para batir da exactamente el mismo resultado que el flujo de cadena.sprintf
contenedor para evitar confusiones.Ah, por cierto, un desafío increíble ... Me he divertido mucho con esto.
Tengo dos algoritmos para enviar (el código está en la parte inferior si tiene ganas de saltarlo). En mis comparaciones, requiero que la función devuelva una cadena y que pueda manejar int y unsigned int. Comparar cosas que no construyen una cadena con las que lo hacen realmente no tiene sentido.
La primera es una implementación divertida que no utiliza ninguna tabla de búsqueda precalculada o división / módulo explícito. Este es competitivo con los otros con gcc y con todos menos Timo's en msvc (por una buena razón que explico a continuación). El segundo algoritmo es mi presentación real para el mayor rendimiento. En mis pruebas, supera a todos los demás en gcc y msvc.
Creo que sé por qué algunos de los resultados en MSVC son muy buenos. std :: string tiene dos constructores relevantes
std::string(char* str, size_t n)
y
std::string(ForwardIterator b, ForwardIterator e)
gcc hace lo mismo para ambos ... es decir, usa el segundo para implementar el primero. El primer constructor puede implementarse significativamente más eficientemente que eso y MSVC lo hace. El beneficio adicional de esto es que, en algunos casos (como mi código rápido y el código de Timo), el constructor de cadenas se puede insertar. De hecho, solo cambiar entre estos constructores en MSVC es casi una diferencia de 2x para mi código.
Mis resultados de las pruebas de rendimiento:
Fuentes de código:
- Voigt
- Timo
- ergosys
- user434507
- user-voigt-timo
- hopman-fun
- hopman-fast
gcc 4.4.5 -O2 en Ubuntu 10.10 de 64 bits, Core i5
MSVC 2010 de 64 bits / Ox en Windows 7 de 64 bits, Core i5
Estos son algunos resultados y un marco de prueba / sincronización en ideone
http://ideone.com/XZRqp
Tenga en cuenta que ideone es un entorno de 32 bits. Mis dos algoritmos sufren de eso, pero hopman_fast es al menos competitivo.
Tenga en cuenta que para aquellos dos o más que no construyen una cadena, agregué la siguiente plantilla de función:
Ahora para mi código ... primero el divertido:
Y luego el rápido:
fuente
Datos de referencia para el código proporcionado en la pregunta:
En ideona (gcc 4.3.4):
Core i7, Windows 7 de 64 bits, 8 GB de RAM, Visual C ++ 2010 de 32 bits:
cl /Ox /EHsc
Core i7, Windows 7 de 64 bits, 8 GB de RAM, Visual C ++ 2010 de 64 bits:
cl /Ox /EHsc
Core i7, Windows 7 de 64 bits, 8 GB de RAM, cygwin gcc 4.3.4:
g++ -O3
editar : iba a agregar mi propia respuesta, pero la pregunta estaba cerrada, así que la agrego aquí. :) Escribí mi propio algoritmo y logré una mejora decente sobre el código de Ben, aunque solo lo probé en MSVC 2010. También hice un punto de referencia de todas las implementaciones presentadas hasta ahora, usando la misma configuración de prueba que estaba en el original de Ben código. - Timo
Intel Q9450, Win XP 32bit, MSVC 2010
cl /O2 /EHsc
-
fuente
std::string
o la mala optimización del código aritmético. Haré otra versión que no se convierta alstd::string
final y veré si a gcc le va mejor.Si bien la información que obtenemos aquí para los algoritmos es bastante buena, creo que la pregunta está "rota", y explicaré por qué creo que esto:
La pregunta pide tomar el rendimiento de
int
->std::string
conversión, y esto puede ser de interés cuando se compara un método comúnmente disponible, como diferentes implementaciones de secuencia de cadena o boost :: lexical_cast. Sin embargo, no tiene sentido cuando se solicita un nuevo código , un algoritmo especializado, para hacer esto. La razón es que int2string siempre involucrará la asignación del montón de std :: string y si estamos tratando de exprimir el último de nuestro algoritmo de conversión, no creo que tenga sentido mezclar estas medidas con las asignaciones del montón realizadas por std: :cuerda. Si quiero una conversión performante, ¡ siempre usaré un búfer de tamaño fijo y ciertamente nunca asignaré nada en el montón!En resumen, creo que los tiempos deberían dividirse:
Estos aspectos no deben mezclarse en un momento, en mi humilde opinión.
fuente
std::string
requisito de "salida como " se colocó allí solo para hacer las cosas justas y consistentes para todas las presentaciones. Los algoritmos que son más rápidos para obtenerstd::string
resultados también serán más rápidos para llenar un búfer preasignado.No puedo probar con VS, pero esto parece ser más rápido que su código para g ++, aproximadamente el 10%. Probablemente podría ajustarse, los valores de decisión elegidos son conjeturas. int solo, lo siento.
fuente
char
aunsigned
produce una mejora de velocidad similar en mi código, al menos en gcc / ideone ideone.com/uthKK . Probaré en VS mañana.Respuesta actualizada del usuario 2985907 ... modp_ufast ...
fuente
Mismatch found: Generated: -99 Reference: -9099999 Mismatch found: Generated: -99 Reference: -9099998 Mismatch found: Generated: -99 Reference: -9099997
Aquí está mi pequeño intento de este divertido rompecabezas.
En lugar de usar tablas de búsqueda, quería que el compilador lo resolviera todo. En este caso en particular, si lee Hackers 'Delight, verá cómo funcionan las divisiones y los módulos, lo que hace que sea muy posible optimizarlo utilizando las instrucciones SSE / AVX.
Punto de referencia de rendimiento
En cuanto a la velocidad, mi punto de referencia aquí me dice que es 1,5 veces más rápido que el trabajo de Timo (en mi Intel Haswell funciona con aproximadamente 1 GB / s).
Cosas que podrías considerar un truco
En cuanto al truco de no hacer una cadena estándar que uso, por supuesto, también lo tomé en consideración para mi punto de referencia del método de Timo.
Yo uso un intrínseco: BSR. Si lo desea, también puede usar tablas de DeBruijn, que es una de las cosas sobre las que escribí bastante en mi publicación '2log más rápida'. Por supuesto, esto tiene una penalización de rendimiento (* bueno ... si estás haciendo muchas operaciones de itoa puedes hacer un BSR más rápido, pero supongo que eso no es justo ...).
La forma en que funciona
Lo primero que debe hacer es calcular cuánta memoria necesitamos. Esto es básicamente un 10log, que se puede implementar de varias maneras inteligentes. Vea los " Hacks Twiddling Bit " frecuentemente citados para más detalles.
Lo siguiente que debe hacer es ejecutar la salida numérica. Utilizo la recursión de plantilla para esto, por lo que el compilador lo resolverá.
Yo uso 'módulo' y 'div' uno al lado del otro. Si lee Hacker's Delight, notará que los dos están estrechamente relacionados, por lo que si tiene una respuesta, probablemente también tenga la otra. Supuse que el compilador puede descubrir los detalles ... :-)
El código
Obteniendo el número de dígitos usando un log10 (modificado):
Obteniendo la cuerda:
fuente
He tenido esto sentado por un tiempo y finalmente pude publicarlo.
Algunos métodos más en comparación con la palabra doble a la vez hopman_fast . Los resultados son para std :: string optimizado de cadena corta del GCC, ya que de lo contrario las diferencias de rendimiento se oscurecen por la sobrecarga del código de gestión de cadena de copia en escritura. El rendimiento se mide de la misma manera que en otras partes de este tema, los recuentos de ciclos son para las partes de serialización sin procesar del código antes de copiar el búfer de salida en una cadena.
Interruptores de tiempo de compilación:
-DVSTRING: habilita cadenas SSO para configuraciones anteriores de GCC
-DBSR1: habilita log10 rápido
-DRDTSC - habilita contadores de ciclos
fuente
Creo que he creado el algoritmo más rápido de entero a cadena. Es una variación del algoritmo Modulo 100 que es aproximadamente un 33% más rápido, y lo más importante es que es más rápido tanto para números pequeños como grandes. Se llama el algoritmo Script ItoS. Para leer el documento que explica cómo diseñé el algoritmo @ver https://github.com/kabuki-starship/kabuki-toolkit/wiki/Engineering-a-Faster-Integer-to-String-Algorithm . Puede usar el algoritmo, pero piense en contribuir de nuevo a la VM de Kabuki y consulte Script ; especialmente si está interesado en los protocolos de red AMIL-NLP y / o definidos por software.
Autor
fuente
std::string
para que la comparación con otros métodos enumerados aquí sea válida. Al principio no pude entender el uso del operador shift para el árbol de búsqueda binario, porque una comparación ya es excepcionalmente rápida, pero ahora me doy cuenta de que sería útil para precalcular ese valor desplazado si lo necesitara. Sin embargo, no lo usas. Por otro lado, no terminas con grandes literales codificados dentro de las instrucciones, así que tal vez esa sea razón suficiente por sí misma.Modificación a la solución del usuario434507. Modificado para usar una matriz de caracteres en lugar de una cadena C ++. Corre un poco más rápido. También moví el cheque por 0 más abajo en el código ... ya que esto nunca sucede para mi caso particular. Muévalo hacia atrás si es más común para su caso.
fuente
Mismatch found: Generated: -9999999990 Reference: -999999999 Mismatch found: Generated: -9999999980 Reference: -999999998 Mismatch found: Generated: -9999999970 Reference: -999999997
Utilizamos el siguiente código (para MSVC):
TBitScanReverse con plantilla:
ayudantes char / wchar_t:
Potencias de 10 mesas:
Impresión real:
El último bucle se puede desenrollar:
La idea principal es la misma que @atlaste sugirió antes: https://stackoverflow.com/a/29039967/2204001
fuente
Acabo de encontrar esto debido a la actividad reciente; Realmente no tengo tiempo para agregar puntos de referencia, pero quería agregar lo que escribí en el pasado para cuando necesito una conversión rápida de enteros a cadenas ...
https://github.com/CarloWood/ai-utils/blob/master/itoa.h
https://github.com/CarloWood/ai-utils/blob/master/itoa.cxx
El truco que se usa aquí es que el usuario debe proporcionar un std :: array que sea lo suficientemente grande (en su pila) y que este código escriba la cadena al revés, comenzando en las unidades y luego devolviendo un puntero al array con un desplazamiento a donde realmente comienza el resultado.
Por lo tanto, esto no asigna ni mueve la memoria, pero aún requiere una división y un módulo por dígito de resultado (que creo que es lo suficientemente rápido, ya que eso es simplemente código ejecutado internamente en la CPU; el acceso a la memoria suele ser el problema en mi humilde opinión).
fuente
¿Por qué nadie usa la función div de stdlib cuando se necesitan tanto cociente como resto?
Usando el código fuente de Timo, terminé con algo como esto:
Ok, para int. Sin signo, la función div no se puede usar, pero se puede manejar por separado.
He definido la macro COPYPAIR de la siguiente manera para probar variaciones sobre cómo copiar los 2 caracteres de digit_pairs (no encontré ninguna ventaja obvia de ninguno de estos métodos):
fuente