c performance glibc scanf

¿Por qué es sscanf de glibc mucho más lento que fscanf en Linux?



performance (2)

Parece que sscanf() glibc explora la cadena fuente para determinar la longitud antes de hacer cualquier otra cosa.

sscanf() (en stdio-common/sscanf.c ) es esencialmente una envoltura alrededor de una llamada a _IO_vsscanf() (en libio/iovsscanf.c ). Y una de las primeras cosas que hace _IO_vsscanf() es inicializar su propia estructura _IO_strfile llamando a _IO_str_init_static_internal() (en libio/strops.c ) que calcula la longitud de la cadena si no se proporciona.

Estoy usando GCC 4.8 y glibc 2.19 en un x86_64 Linux.

Mientras jugaba con diferentes métodos de entrada para una pregunta diferente , fscanf y sscanf . Específicamente, yo usaría fscanf en la entrada estándar directamente:

char s[128]; int n; while (fscanf(stdin, "%127s %d", s, &n) == 2) { }

O primero leería toda la entrada en un búfer y luego atravesaría el búfer con sscanf . (Leer todo en el búfer lleva una pequeña cantidad de tiempo).

char s[128]; int n; char const * p = my_data; for (int b; sscanf(p, "%127s %d%n", s, &n, &b) == 2; p += b) { }

Para mi sorpresa, la versión fscanf es mucho más rápida. Por ejemplo, el procesamiento de varias decenas de miles de líneas con fscanf toma este tiempo:

10000 0.003927487 seconds time elapsed 20000 0.006860206 seconds time elapsed 30000 0.007933329 seconds time elapsed 40000 0.012881912 seconds time elapsed 50000 0.013516816 seconds time elapsed 60000 0.015670432 seconds time elapsed 70000 0.017393129 seconds time elapsed 80000 0.019837480 seconds time elapsed 90000 0.023925753 seconds time elapsed

Ahora lo mismo con sscanf :

10000 0.035864643 seconds time elapsed 20000 0.127150772 seconds time elapsed 30000 0.319828373 seconds time elapsed 40000 0.611551668 seconds time elapsed 50000 0.919187459 seconds time elapsed 60000 1.327831544 seconds time elapsed 70000 1.809843039 seconds time elapsed 80000 2.354809588 seconds time elapsed 90000 2.970678416 seconds time elapsed

Estaba usando las herramientas de perf de Google para medir esto. Por ejemplo, para 50000 líneas, el código fscanf requiere aproximadamente 50M ciclos, y el código sscanf aproximadamente 3300M ciclos. Así que desglosé los sitios de llamadas principales con el perf report perf record / perf report . Con fscanf :

35.26% xf libc-2.19.so [.] _IO_vfscanf 23.91% xf [kernel.kallsyms] [k] 0xffffffff8104f45a 8.93% xf libc-2.19.so [.] _int_malloc

Y con sscanf :

98.22% xs libc-2.19.so [.] rawmemchr 0.68% xs libc-2.19.so [.] _IO_vfscanf 0.38% xs [kernel.kallsyms] [k] 0xffffffff8104f45a

¡Así que casi todo el tiempo con sscanf se gasta en rawmemchr ! ¿Por qué es esto? ¿Cómo puede el código fscanf evitar este costo?

Intenté buscar esto, pero lo mejor que pude encontrar es esta discusión de llamadas de realloc bloqueadas que no creo que se aplique aquí. También pensé que fscanf tiene una mejor fscanf memoria (usando el mismo búfer una y otra vez), pero eso no puede hacer una gran diferencia.

¿Alguien tiene alguna idea en esta extraña discrepancia?


sscanf () convierte la cadena que pasa a un _IO_FILE* para que la cadena se vea como un "archivo". Esto es así, el mismo _IO_vfscanf () interno se puede usar tanto para una cadena como para un ARCHIVO *.

Sin embargo, como parte de esa conversión, realizada en una función _IO_str_init_static_internal (), llama a __rawmemchr (ptr, ''/0''); esencialmente una llamada strlen (), en su cadena de entrada. Esta conversión se realiza en cada llamada a sscanf (), y como su búfer de entrada es bastante grande, pasará una buena cantidad de tiempo calculando la longitud de la cadena de entrada.

Crear un ARCHIVO * a partir de la cadena de entrada usando fmemopen () y usar fscanf () podría ser otra alternativa.