leer - tipos de datos en c




¿Varias llamadas a printf() frente a una llamada a printf() con una cadena larga? (4)

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?


¿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)
[...]


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 .


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 .





printf