2008-11-07 7 views
6

A través del perfilado, he descubierto que el sprintf aquí lleva mucho tiempo. ¿Hay una alternativa de mejor rendimiento que maneje los ceros a la izquierda en los campos y/m/d h/m/s?¿Cómo puedo mejorar/reemplazar el sprintf, que he medido para ser un hotspot de rendimiento?

SYSTEMTIME sysTime; 
GetLocalTime(&sysTime); 
char buf[80]; 
for (int i = 0; i < 100000; i++) 
{ 

    sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d", 
     sysTime.wYear, sysTime.wMonth, sysTime.wDay, 
     sysTime.wHour, sysTime.wMinute, sysTime.wSecond); 

} 

Nota: El PO explica en los comentarios que este es un ejemplo reducidos al mínimo. El bucle "real" contiene un código adicional que usa valores de tiempo variables de una base de datos. La creación de perfiles ha identificado sprintf() como delincuente.

+0

¿Cuánto dura "un largo tiempo? "¿?" Esperaría que fuera en microsegundos en lugar de milisegundos (dependiendo de la CPU) – Roddy

+0

Será mejor que encuentre una manera de llamar al sprintf con menos frecuencia. – Brian

Respuesta

18

Si estaba escribiendo su propia función para hacer el trabajo, una tabla de búsqueda de los valores de cadena de 0 .. 61 evitaría tener que hacer cualquier aritmética para todo excepto el año.

edición: Nótese que para hacer frente a los segundos intercalares (y para que coincida con strftime()) debe ser capaz de imprimir los valores de segundos 60 y 61.

char LeadingZeroIntegerValues[62][] = { "00", "01", "02", ... "59", "60", "61" }; 

Por otra parte, ¿qué hay de strftime()? No tengo idea de cómo se compara el rendimiento (podría estar llamando a sprintf()), pero vale la pena mirarlo (y podría estar haciendo la búsqueda anterior).

+3

+1 de un antiguo ingeniero incorporado. Cuando el tiempo es más crítico que el tamaño, ¡es difícil superar una tabla de búsqueda! –

+0

Agradable. Pero ten cuidado de cómo usas esto. Llamar a strcat() para anexar las cadenas también es probable que sea feo, en cuanto al rendimiento. – Roddy

+0

Sí, ya que sabes que son todos los 2 caracteres, solo puedes memcpy. Además, algo que me acaba de ocurrir al mirar la página strtime, la matriz de cadenas probablemente debería ser hasta "61", ya que strtime hace eso, presumiblemente para dar cuenta de los segundos intercalares. –

6

usted podría intentar llenar cada caracter en la salida a su vez.

buf[0] = (sysTime.wYear/1000) % 10 + '0' ; 
buf[1] = (sysTime.wYear/100) % 10 + '0'; 
buf[2] = (sysTime.wYear/10) % 10 + '0'; 
buf[3] = sysTime.wYear % 10 + '0'; 
buf[4] = '-'; 

... etc ...

No es bonita, pero se obtiene la imagen. Si nada más, puede ayudar a explicar por qué sprintf no va a ser tan rápido.

OTOH, tal vez podría almacenar en caché el último resultado. De esa forma solo necesitarías generar uno por segundo.

+0

En la vida real, el valor del tiempo es diferente en cada iteración del ciclo (desde un db). Simplemente simplifiqué el código para el ejemplo, –

6

Printf debe tratar con una gran cantidad de formatos diferentes. Sin duda podría tomar el source for printf y utilizarlo como base para rodar su propia versión que trata específicamente con la estructura sysTime. De esa manera, pasa un argumento, y hace exactamente el trabajo que necesita hacerse y nada más.

1

Es probable que obtenga un aumento gradual a mano al pasar una rutina que establece los dígitos en el retorno buf, ya que podría evitar el análisis repetido de una cadena de formato y no tendría que lidiar con muchos de los casos más complejos sprintf maneja. Sin embargo, detesta recomendarlo.

Yo recomendaría tratando de averiguar si de alguna manera se puede reducir la cantidad que necesita para generar estas cadenas, que son opcionales somegtimes, que pueden almacenarse en caché, etc.

0

Es difícil imaginar que vas para vencer a sprintf en enteros enteros. ¿Estás seguro de que sprintf es tu problema?

+0

No está formateando enteros en los que sprintf toma su tiempo, está analizando la cadena de formato. Como no cambia en el ciclo, no es necesario analizarlo cada vez. –

2

¿Qué quiere decir por un tiempo "largo" - ya que el sprintf() es la única declaración en su bucle y la "fontanería" del bucle (incremento, comparación) es insignificante, la sprintf() tiene que consumir el más tiempo.

¿Recuerdas el viejo chiste sobre el hombre que perdió su anillo de bodas en 3rd Street una noche, pero lo buscó el 5 porque la luz era más brillante allí? Ha creado un ejemplo que está diseñado para "probar" su suposición de que sprintf() es ineficaz.

Tus resultados serán más precisos si cuentas el código "real" que contiene sprintf() además de todas las demás funciones y algoritmos que usas. De forma alternativa, intente escribir su propia versión que aborde la conversión numérica cero específica que necesita.

Puede que se sorprenda de los resultados.

+0

El fragmento de código es un ejemplo simplificado. En el ejemplo real, hay muchas cosas en el circuito. (char *) _bstr_t, itoa ... Es el sprint aquí lo que es malo. –

+0

¿Fue Einstein quien dijo que todo debería hacerse lo más simple posible, pero no más simple? :-) Editó su pregunta para reflejar esto. –

1

¿Qué tal el almacenamiento en caché de los resultados? ¿No es eso una posibilidad? Teniendo en cuenta que esta llamada particular de sprintf() se realiza con demasiada frecuencia en su código, supongo que entre la mayoría de estas llamadas consecutivas, el año, el mes y el día no cambian.

Por lo tanto, podemos implementar algo como lo siguiente. Declarar una estructura SYSTEMTIME corriente de edad y:

SYSTEMTIME sysTime, oldSysTime; 

Además, declarar partes separadas para mantener la fecha y la hora:

char datePart[80]; 
char timePart[80]; 

Para la primera vez, tendrá que rellenar tanto sysTime, oldSysTime, así como datePart y timePart. Pero sprintf() posterior 's se pueden hacer bastante rápido como se indica a continuación:

sprintf (timePart, "%02d:%02d:%02d", sysTime.wHour, sysTime.wMinute, sysTime.wSecond); 
if (oldSysTime.wYear == sysTime.wYear && 
    oldSysTime.wMonth == sysTime.wMonth && 
    oldSysTime.wDay == sysTime.wDay) 
    { 
    // we can reuse the date part 
    strcpy (buff, datePart); 
    strcat (buff, timePart); 
    } 
else { 
    // we need to regenerate the date part as well 
    sprintf (datePart, "%4d-%02d-%02d", sysTime.wYear, sysTime.wMonth, sysTime.wDay); 
    strcpy (buff, datePart); 
    strcat (buff, timePart); 
} 

memcpy (&oldSysTime, &sysTime, sizeof (SYSTEMTIME)); 

Por encima de código tiene algo de redundancia para que el código sea más fácil de entender. Puede factorizar fácilmente. Puede acelerar aún más si sabe que incluso las horas y los minutos no cambiarán más rápido que su llamada a la rutina.

+0

no. Mi fragmento de código está simplificado de lo real. –

1

me gustaría hacer algunas cosas ...

  • caché de la hora actual por lo que no tiene que regenerar la marca de tiempo cada vez que
  • hacer la conversión hora manualmente. La parte más lenta de las printf -funciones familiares es el análisis de cadenas de formato, y es absurdo dedicar ciclos a ese análisis en cada ejecución de bucle.
  • intente utilizar tablas de búsqueda de 2 bytes para todas las conversiones ({ "00", "01", "02", ..., "99" }). Esto se debe a que desea evitar la aritmética de módulo, y una tabla de 2 bytes significa que solo debe usar un módulo para el año.
2

Parece que Jaywalker está sugiriendo un método muy similar (me gana menos de una hora).

Además del método de tabla de búsqueda ya sugerido (matriz n2s [] a continuación), ¿qué hay de generar su buffer de formato para que el sprintf habitual sea menos intensivo? El código siguiente solo deberá completar el minuto y el segundo cada vez que pase el ciclo, a menos que el año/mes/día/hora haya cambiado. Obviamente, si alguno de ellos ha cambiado, sí recibe otro golpe de sprintf, pero en general puede que no sea más de lo que actualmente está presenciando (cuando se combina con la búsqueda de matriz).


static char fbuf[80]; 
static SYSTEMTIME lastSysTime = {0, ..., 0}; // initialize to all zeros. 

for (int i = 0; i < 100000; i++) 
{ 
    if ((lastSysTime.wHour != sysTime.wHour) 
    || (lastSysTime.wDay != sysTime.wDay) 
    || (lastSysTime.wMonth != sysTime.wMonth) 
    || (lastSysTime.wYear != sysTime.wYear)) 
    { 
     sprintf(fbuf, "%4d-%02s-%02s %02s:%%02s:%%02s", 
       sysTime.wYear, n2s[sysTime.wMonth], 
       n2s[sysTime.wDay], n2s[sysTime.wHour]); 

     lastSysTime.wHour = sysTime.wHour; 
     lastSysTime.wDay = sysTime.wDay; 
     lastSysTime.wMonth = sysTime.wMonth; 
     lastSysTime.wYear = sysTime.wYear; 
    } 

    sprintf(buf, fbuf, n2s[sysTime.wMinute], n2s[sysTime.wSecond]); 

} 
+0

Nuevo en esto ... ¿mi respuesta completa aparece para cualquiera? Para mí, parece que solo alrededor de 4 líneas son visibles. – shank

+0

necesita usar < for < and > para> – quinmars

+0

Aah, por supuesto. Gracias. – shank

1

Estoy trabajando en un problema similar en este momento.

Necesito registrar declaraciones de depuración con marca de tiempo, nombre de archivo, número de línea, etc. en un sistema integrado. Ya tenemos un registrador en su lugar, pero cuando giro la perilla a "registro completo", se come todos nuestros ciclos de proceso y pone nuestro sistema en estados desesperados, afirma que ningún dispositivo informático debería tener que experimentar alguna vez.

Alguien dijo "No se puede medir/observar algo sin cambiar lo que está midiendo/observando".

Así que estoy cambiando las cosas para mejorar el rendimiento. El estado actual de las cosas es que Im 2 veces más rápido que la función original llamada (el cuello de botella en ese sistema de registro no está en la llamada de función sino en el lector de registro que es un ejecutable separado, que puedo descartar si escribo el mío pila de registro).

La interfaz que necesito proporcionar es algo así como - void log(int channel, char *filename, int lineno, format, ...). Necesito adjuntar el nombre del canal (que actualmente hace una búsqueda lineal dentro de una lista! Por cada declaración de depuración!) Y la marca de tiempo que incluye contador de milisegundos. Estas son algunas de las cosas que estoy haciendo para hacerlo más rápido:

  • Nombre del canal Stringify para poder strcpy en lugar de buscar en la lista. define macro LOG(channel, ...etc) como log(#channel, ...etc). Puede utilizar memcpy si se fija la longitud de la cadena mediante la definición de LOG(channel, ...)log("...."#channel - sizeof("...."#channel) + *11*) de ser arreglados canal de bytes longitudes
  • Generar una cadena de marca de tiempo de un par de veces por segundo. Puedes usar asctime o algo así. A continuación, agregue la cadena de longitud fija a cada instrucción de depuración.
  • Si desea generar la cadena de marca de tiempo en tiempo real, una tabla de búsqueda con asignación (¡no memcpy!) Es perfecta. Pero eso solo funciona para números de 2 dígitos y quizás para el año.
  • ¿Qué hay de tres dígitos (milisegundos) y cinco dígitos (lineno)? No me gusta itoa y no me gusta el itoa personalizado (digit = ((value /= value) % 10)) ya sea porque divs y mods son lento. Escribí las funciones a continuación y luego descubrí que algo similar está en el manual de optimización de AMD (en ensamblaje), lo que me da la confianza de que se trata de las implementaciones de C más rápidas.

    void itoa03(char *string, unsigned int value) 
    { 
        *string++ = '0' + ((value = value * 2684355) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = ' ';/* null terminate here if thats what you need */ 
    } 
    

    Del mismo modo, para los números de línea,

    void itoa05(char *string, unsigned int value) 
    { 
        *string++ = ' '; 
        *string++ = '0' + ((value = value * 26844 + 12) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = '0' + ((value = ((value & 0x0FFFFFFF)) * 10) >> 28); 
        *string++ = ' ';/* null terminate here if thats what you need */ 
    } 
    

En general, mi código es bastante rápido ahora. El vsnprintf() que necesito usar toma aproximadamente el 91% del tiempo y el resto de mi código toma solo el 9% (mientras que el resto del código es decir, excepto vsprintf() solía tomar el 54% antes)

+0

Esto es genial, muchas gracias –

+0

¿Puedes explicar esos números mágicos? – someonewithpc

Cuestiones relacionadas