leer - scanf
¿Varias llamadas a printf() frente a una llamada a printf() con una cadena larga? (5)
¿Cuáles son los costos incurridos por este estilo en comparación con tener varios printf () para cada línea?
Múltiple printf
dará lugar a múltiples llamadas de función y esa es la única sobrecarga.
¿Habría un posible desbordamiento de pila si la cadena es demasiado larga?
No hay desbordamiento de pila en este caso. Los literales de cadena generalmente se almacenan en memoria de solo lectura, no en memoria de pila. Cuando se pasa una cadena a printf
, solo se copia un puntero a su primer elemento en la pila.
El compilador tratará esta cadena de múltiples líneas "línea 1 / n"
"line 2/n"
"line 3/n"
"line 4/n"
"line 5/n"
"line 6/n"
"line 7/n"
"line 8/n"
"line 9/n.. etc"
como una sola cuerda
"line 1/nline 2/nline 3/nline 4/nline 5/nline 6/nline 7/nline 8/nline 9/n.. etc"
y esto se almacenará en la sección de solo lectura de la memoria.
Pero tenga en cuenta que (señalado por pmg en un comment ) la sección 5.2.4.1 de la norma C11 dice que los límites de traducción dicen que
La implementación deberá poder traducir y ejecutar al menos un programa que contenga al menos una instancia de cada uno de los siguientes límites 18) :
[...]
- 4095 caracteres en una cadena literal (después de la concatenación)
[...]
Digamos que tengo una línea de printf()
con una cadena larga:
printf( "line 1/n"
"line 2/n"
"line 3/n"
"line 4/n"
"line 5/n"
"line 6/n"
"line 7/n"
"line 8/n"
"line 9/n.. etc");
¿Cuáles son los costos incurridos por este estilo en comparación con tener varios printf()
para cada línea?
¿Habría un posible desbordamiento de pila si la cadena es demasiado larga?
C concatena literales de cadena si están separados por nada o por espacios en blanco. Tan abajo
printf( "line 1/n"
"line 2/n"
"line 3/n"
"line 4/n"
"line 5/n"
"line 6/n"
"line 7/n"
"line 8/n"
"line 9/n.. etc");
Está perfectamente bien y se destaca en el punto de vista de la legibilidad. Además, una sola llamada a printfs sin duda tiene menos gastos generales que 9 llamadas a printf
.
Cada printf adicional (o pone si su compilador lo optimiza de esa manera) incurrirá en la sobrecarga de la función específica del sistema cada vez, aunque hay una buena probabilidad de que la optimización los combine de todos modos.
Todavía no he visto una implementación de printf que fuera una función de hoja, por lo que espero gastos generales de llamada a funciones adicionales para algo como vfprintf y sus clientes.
Entonces es probable que tenga algún tipo de sobrecarga de llamadas al sistema para cada escritura. Como Printf usa stdout, que está almacenado en búfer, algunos de estos cambios de contexto (muy costosos) normalmente podrían evitarse ... excepto que todos los ejemplos anteriores terminan con nuevas líneas. La mayor parte de su costo probablemente estará aquí.
Si está realmente preocupado por el costo en su subproceso principal, mueva este tipo de cosas a un subproceso separado.
Un printf
sin modificadores de formato se reemplaza silenciosamente (también conocido como. Optimizado) a una llamada de puts
. Esto ya es una aceleración. Realmente no quieres perder eso al llamar a printf
/ puts
varias veces.
GCC tiene printf
(entre otros) como una función integrada, por lo que puede optimizar las llamadas durante el tiempo de compilación.
Ver:
printf
es una función lenta si solo está generando cadenas constantes, porque printf
tiene que escanear cada carácter para un especificador de formato ( %
). Las funciones como puts
son significativamente más rápidas para cadenas largas, ya que, básicamente, solo pueden memcpy
la cadena de entrada en el búfer de E / S de salida.
Muchos compiladores modernos (GCC, Clang, probablemente otros) tienen una optimización que convierte automáticamente printf
en entradas si la cadena de entrada es una cadena constante sin especificadores de formato que termina con una nueva línea. Entonces, por ejemplo, compilando el siguiente código:
printf("line 1/n");
printf("line 2/n");
printf("line 3"); /* no newline */
da como resultado el siguiente ensamblaje (Clang 703.0.31, cc test.c -O2 -S
):
...
leaq L_str(%rip), %rdi
callq _puts
leaq L_str.3(%rip), %rdi
callq _puts
leaq L_.str.2(%rip), %rdi
xorl %eax, %eax
callq _printf
...
en otras palabras, puts("line 1"); puts("line 2"); printf("line 3");
puts("line 1"); puts("line 2"); printf("line 3");
.
Si su printf
larga de printf
no termina con una nueva línea, entonces su rendimiento podría ser significativamente peor que si hubiera hecho un montón de llamadas a printf
con cadenas terminadas en nueva línea, simplemente debido a esta optimización. Para demostrarlo, considere el siguiente programa:
#include <stdio.h>
#define S "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
#define L S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S
/* L is a constant string of 4000 ''a''s */
int main() {
int i;
for(i=0; i<1000000; i++) {
#ifdef SPLIT
printf(L "/n");
printf(S);
#else
printf(L "/n" S);
#endif
}
}
Si SPLIT
no está definido (produciendo un solo printf
sin una nueva línea de terminación), el tiempo se ve así:
[08/11 11:47:23] /tmp$ cc test.c -O2 -o test
[08/11 11:47:28] /tmp$ time ./test > /dev/null
real 0m2.203s
user 0m2.151s
sys 0m0.033s
Si se define SPLIT
(que produce dos printf
s, uno con una nueva línea de terminación, el otro sin), el tiempo se ve así:
[08/11 11:48:05] /tmp$ time ./test > /dev/null
real 0m0.470s
user 0m0.435s
sys 0m0.026s
Como puede ver, en este caso, dividir el printf
en dos partes en realidad produce una aceleración de 4x. Por supuesto, este es un caso extremo, pero ilustra cómo se puede optimizar de forma variable la impresión dependiendo de la entrada. (Tenga en cuenta que usar fwrite
es incluso más rápido, 0.197, por lo que debería considerar usarlo si realmente quiere velocidad).
tl; dr: si está imprimiendo solo cadenas grandes y constantes, evite printf
completo y use una función más rápida, como puts
o fwrite
.