Gran diferencia de velocidad de fprintf sin "-std=c99"
performance locking (4)
Desde MinGW32 3.15, las funciones de printf
compatibles están disponibles para usar en lugar de las que se encuentran en el tiempo de ejecución de Microsoft C (CRT). Las nuevas funciones de printf
se utilizan al compilar en estrictos modos ANSI, POSIX y / o C99.
Para más información vea el registro de cambios de mingw32.
Puede usar __msvcrt_fprintf()
para usar la función rápida (no compatible).
Había estado luchando durante semanas con un traductor de bajo rendimiento que había escrito. En el siguiente bechmark simple
#include<stdio.h>
int main()
{
int x;
char buf[2048];
FILE *test = fopen("test.out", "wb");
setvbuf(test, buf, _IOFBF, sizeof buf);
for(x=0;x<1024*1024; x++)
fprintf(test, "%04d", x);
fclose(test);
return 0
}
vemos el siguiente resultado
bash-3.1$ gcc -O2 -static test.c -o test
bash-3.1$ time ./test
real 0m0.334s
user 0m0.015s
sys 0m0.016s
Como puede ver, en el momento en que se agrega el indicador "-std = c99", el rendimiento se derrumba:
bash-3.1$ gcc -O2 -static -std=c99 test.c -o test
bash-3.1$ time ./test
real 0m2.477s
user 0m0.015s
sys 0m0.000s
El compilador que estoy usando es gcc 4.6.2 mingw32.
El archivo generado es de aproximadamente 12M, por lo que esta es una diferencia de aproximadamente 21MB / s entre los dos.
La ejecución de diff
muestra que los archivos generados son idénticos.
Supuse que esto tiene algo que ver con el bloqueo de archivos en fprintf
, de los cuales el programa hace un uso intensivo, pero no he podido encontrar una manera de desactivarlo en la versión C99.
Intenté flockfile
en el flujo que uso al principio del programa, y un funlockfile
correspondiente al final, pero funlockfile
errores del compilador sobre declaraciones implícitas y errores de vinculador que afirman referencias indefinidas a esas funciones.
¿Podría haber otra explicación para este problema y, lo que es más importante, hay alguna forma de usar C99 en Windows sin pagar un precio tan alto por el rendimiento?
Editar:Después de ver el código generado por estas opciones, parece que en las versiones lentas, mingw se pega en lo siguiente:
_fprintf:
LFB0:
.cfi_startproc
subl $28, %esp
.cfi_def_cfa_offset 32
leal 40(%esp), %eax
movl %eax, 8(%esp)
movl 36(%esp), %eax
movl %eax, 4(%esp)
movl 32(%esp), %eax
movl %eax, (%esp)
call ___mingw_vfprintf
addl $28, %esp
.cfi_def_cfa_offset 4
ret
.cfi_endproc
En la versión rápida, esto simplemente no existe; De lo contrario, ambos son exactamente iguales. Supongo que __mingw_vfprintf
parece ser el lenguaje lento aquí, pero no tengo idea de qué comportamiento debe emular, lo que lo hace tan lento.
Después de considerar a su ensamblador, parece que la versión lenta está usando la implementación *printf()
de MinGW, basada indudablemente en la versión GCC, mientras que la versión rápida está usando la implementación de Microsoft desde msvcrt.dll
.
Ahora, el MS one es notablemente por carecer de muchas características, que el GCC implementa. Algunas de estas son extensiones GNU, pero otras son para conformidad con C99. Y como está utilizando -std=c99
, está solicitando la conformidad.
Pero ¿por qué tan lento? Bueno, un factor es la simplicidad, la versión MS es mucho más simple, por lo que se espera que se ejecute más rápido, incluso en los casos triviales. Otro factor es que se está ejecutando bajo Windows, por lo que se espera que la versión de MS sea más eficiente que la copia del mundo Unix.
¿Explica un factor de x10? Probablemente no...
Otra cosa que puedes probar:
- Reemplace
fprintf()
consprintf()
, imprimiendo en un búfer de memoria sin tocar el archivo en absoluto. Entonces puedes intentar hacerfwrite()
sin printfing . De esa manera puede adivinar si la pérdida está en el formato de los datos o en la escritura delFILE
.
Después de investigar el código fuente, descubrí por qué la función MinGW es terriblemente lenta:
Al comienzo de un [v,f,s]printf
en MinGW, hay un código de inicialización de aspecto inocente:
__pformat_t stream = {
dest, /* output goes to here */
flags &= PFORMAT_TO_FILE | PFORMAT_NOLIMIT, /* only these valid initially */
PFORMAT_IGNORE, /* no field width yet */
PFORMAT_IGNORE, /* nor any precision spec */
PFORMAT_RPINIT, /* radix point uninitialised */
(wchar_t)(0), /* leave it unspecified */
0, /* zero output char count */
max, /* establish output limit */
PFORMAT_MINEXP /* exponent chars preferred */
};
Sin embargo, PFORMAT_MINEXP
no es lo que parece ser:
#ifdef _WIN32
# define PFORMAT_MINEXP __pformat_exponent_digits()
# ifndef _TWO_DIGIT_EXPONENT
# define _get_output_format() 0
# define _TWO_DIGIT_EXPONENT 1
# endif
static __inline__ __attribute__((__always_inline__))
int __pformat_exponent_digits( void )
{
char *exponent_digits = getenv( "PRINTF_EXPONENT_DIGITS" );
return ((exponent_digits != NULL) && ((unsigned)(*exponent_digits - ''0'') < 3))
|| (_get_output_format() & _TWO_DIGIT_EXPONENT)
? 2
: 3
;
}
Esto termina siendo llamado cada vez que quiero imprimir, y getenv
en Windows no debe ser muy rápido. Reemplazar esa definición con un 2
devuelve el tiempo de ejecución a donde debería estar.
Entonces, la respuesta se reduce a esto: cuando se usa -std=c99
o cualquier modo compatible con ANSI, MinGW cambia el tiempo de ejecución de CRT por su propia cuenta. Normalmente, esto no sería un problema, pero la biblioteca de MinGW tenía un error que ralentizaba sus funciones de formato mucho más allá de cualquier cosa imaginable.
Usando -std=c99
deshabilita todas las extensiones GNU.
Con las extensiones de GNU y la optimización, su fprintf(test, "B")
probablemente sea reemplazado por una fputc(''B'', test)
Tenga en cuenta que esta respuesta es obsoleta, consulte https://.com/a/13973562/611560 y https://.com/a/13973933/611560