mezcla de cout e printf para una salida más rápida


Después de realizar algunas pruebas me di cuenta de que printf es mucho más rápido que cout. Sé que depende de la implementación, pero en mi Linux box printf es 8 veces más rápido. Así que mi idea es mezclar los dos métodos de impresión: quiero usar cout para impresiones simples, y planeo usar printf para producir salidas enormes (típicamente en un bucle). Creo que es seguro hacerlo siempre y cuando no me olvide de enjuagar antes de cambiar al otro método:

cout << "Hello" << endl;
cout.flush();

for (int i=0; i<1000000; ++i) {
    printf("World!\n");
}
fflush(stdout);

cout << "last line" << endl;
cout << flush;

¿Está bien así?

Actualización: Gracias por todos los valiosos comentarios. Resumen de las respuestas: si desea evitar soluciones complicadas, simplemente no use endl con cout ya que vacía el búfer implícitamente. Use "\n" en su lugar. Puede ser interesante si produce grandes salidas .

Author: Jabba, 2009-12-17

9 answers

La respuesta directa es que sí, está bien.

Mucha gente ha lanzado varias ideas sobre cómo mejorar la velocidad, pero parece haber un poco de desacuerdo sobre cuál es el más efectivo. Decidí escribir un programa de prueba rápida para tener al menos alguna idea de qué técnicas hicieron qué.

#include <iostream>
#include <string>
#include <sstream>
#include <time.h>
#include <iomanip>
#include <algorithm>
#include <iterator>
#include <stdio.h>

char fmt[] = "%s\n";
static const int count = 3000000;
static char const *const string = "This is a string.";
static std::string s = std::string(string) + "\n";

void show_time(void (*f)(), char const *caption) { 
    clock_t start = clock();
    f();
    clock_t ticks = clock()-start;
    std::cerr << std::setw(30) << caption 
        << ": " 
        << (double)ticks/CLOCKS_PER_SEC << "\n";
}

void use_printf() {
    for (int i=0; i<count; i++)
        printf(fmt, string);
}

void use_puts() {
    for (int i=0; i<count; i++) 
        puts(string);        
}

void use_cout() { 
    for (int i=0; i<count; i++)
        std::cout << string << "\n";
}

void use_cout_unsync() { 
    std::cout.sync_with_stdio(false);
    for (int i=0; i<count; i++)
        std::cout << string << "\n";
    std::cout.sync_with_stdio(true);
}

void use_stringstream() { 
    std::stringstream temp;
    for (int i=0; i<count; i++)
        temp << string << "\n";
    std::cout << temp.str();
}

void use_endl() { 
    for (int i=0; i<count; i++)
        std::cout << string << std::endl;
}

void use_fill_n() { 
    std::fill_n(std::ostream_iterator<char const *>(std::cout, "\n"), count, string);
}

void use_write() {
    for (int i = 0; i < count; i++)
        std::cout.write(s.data(), s.size());
}

int main() { 
    show_time(use_printf, "Time using printf");
    show_time(use_puts, "Time using puts");
    show_time(use_cout, "Time using cout (synced)");
    show_time(use_cout_unsync, "Time using cout (un-synced)");
    show_time(use_stringstream, "Time using stringstream");
    show_time(use_endl, "Time using endl");
    show_time(use_fill_n, "Time using fill_n");
    show_time(use_write, "Time using write");
    return 0;
}

Ejecuté esto en Windows después de compilar con VC++ 2013 (versiones x86 y x64). La salida de una ejecución (con la salida redirigida a un archivo de disco) parecía esto:

          Time using printf: 0.953
            Time using puts: 0.567
   Time using cout (synced): 0.736
Time using cout (un-synced): 0.714
    Time using stringstream: 0.725
            Time using endl: 20.097
          Time using fill_n: 0.749
           Time using write: 0.499

Como era de esperar, los resultados varían, pero hay algunos puntos que me parecen interesantes:

  1. printf/puts son mucho más rápidos que cout cuando se escribe en el dispositivo NUL
  • pero cout se mantiene bastante bien cuando se escribe en un archivo real
  • Bastantes optimizaciones propuestas logran poco
    • En mis pruebas, fill_n es casi tan rápido como cualquier otra cosa
  • Con mucho, la mayor optimización es evitar endl
  • cout.escribir dio el tiempo más rápido (aunque probablemente no por un margen significativo
  • Recientemente he editado el código para forzar una llamada a printf. Anders Kaseorg tuvo la amabilidad de señalar {que g++ reconoce la secuencia específica printf("%s\n", foo); es equivalente a puts(foo);, y genera código en consecuencia (es decir, genera código para llamar a puts en lugar de printf). Mover la cadena de formato a una matriz global, y pasarla como la cadena de formato produce una salida idéntica, pero la obliga a ser producido vía printf en lugar de puts. Por supuesto, es posible que también optimicen esto algún día, pero al menos por ahora (g++ 5.1) una prueba con g++ -O3 -S confirma que en realidad está llamando a printf (donde el código anterior compiló una llamada a puts).

     68
    Author: Jerry Coffin,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2015-06-16 23:26:07

    Enviar std::endl a la secuencia añade un newline y limpia la secuencia. La invocación posterior de cout.flush() es superflua. Si esto se hizo cuando el tiempo cout vs. printf entonces usted no estaba comparando manzanas con manzanas.

     19
    Author: William Bell,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2010-08-20 18:05:44

    De forma predeterminada, los flujos de salida estándar de C y C++ están sincronizados, de modo que escribir en uno causa un flush del otro, por lo que no se necesitan flushes explícitos.

     12
    Author: ,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2009-12-17 21:09:48

    También, tenga en cuenta que la secuencia de C++ se sincroniza con la secuencia de C.
    Por lo tanto, hace un trabajo extra para mantenerse sincronizado.

    Otra cosa a tener en cuenta es asegurarse de limpiar los flujos de una cantidad igual. Si continuamente enjuaga la corriente en un sistema y no en el otro, eso definitivamente afectará la velocidad de las pruebas.

    Antes de asumir que uno es más rápido que el otro debes:

    • un-sync C++ I/O from C I/O (see sync_with_stdio() ).
    • Asegúrese de que la cantidad de flushes es comparable.
     12
    Author: Martin York,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2018-06-12 03:36:36

    Puede mejorar aún más el rendimiento de printf aumentando el tamaño del búfer para stdout:

    setvbuf (stdout, NULL, _IOFBF, 32768);  // any value larger than 512 and also a
                      // a multiple of the system i/o buffer size is an improvement
    

    El número de llamadas al sistema operativo para realizar e/s es casi siempre el componente más caro y el limitador de rendimiento.

    Por supuesto, si la salida cout se entremezcla con stdout, los flujos de búfer frustran el propósito de un tamaño de búfer mayor.

     9
    Author: wallyk,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2009-12-18 03:59:08

    Puede utilizar sync_with_stdio para hacer C++ IO más rápido.

    cout.sync_with_stdio(false);
    

    Debería mejorar su rendimiento de salida con cout.

     3
    Author: Juan,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2009-12-17 21:08:07

    No te preocupes por el rendimiento entre printf y cout. Si desea obtener rendimiento, separe la salida formateada de la salida no formateada.

    puts("Hello World\n") es mucho más rápido que printf("%s", "Hellow World\n"). (Principalmente debido a la sobrecarga de formato). Una vez que haya aislado el formato del texto sin formato, puede hacer trucos como:

    const char hello[] = "Hello World\n";
    cout.write(hello, sizeof(hello) - sizeof('\0'));
    

    Para acelerar la salida formateada, el truco es realizar todo el formato a una cadena, luego usar la salida de bloque con la cadena (o búfer):

    const unsigned int MAX_BUFFER_SIZE = 256;
    char buffer[MAX_BUFFER_SIZE];
    sprintf(buffer, "%d times is a charm.\n", 5);
    unsigned int text_length = strlen(buffer) - sizeof('\0');
    fwrite(buffer, 1, text_length, stdout);
    

    Para mejorar aún más el rendimiento de su programa, reduzca la cantidad de salida. Cuanto menos material de salida, más rápido será su programa. Un efecto secundario será que el tamaño del ejecutable también se reducirá.

     3
    Author: Thomas Matthews,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2009-12-17 22:08:19

    Bueno, no se me ocurre ninguna razón para usar cout para ser honesto. Es completamente loco tener una plantilla enorme y voluminosa para hacer algo tan simple que estará en cada archivo. Además, es como si estuviera diseñado para ser lo más lento posible para escribir y después de la millonésima vez de escribir variableName>>> en accidente nunca quiero hacer eso de nuevo.

    Sin mencionar que si incluye espacio de nombres std, el mundo eventualmente lo hará implosiona, y si no lo haces tu carga de tecleo se vuelve aún más ridícula.

    Sin embargo, tampoco me gusta mucho printf. Para mí, la solución es crear mi propia clase concreta y luego llamar a cualquier cosa de io que sea necesario dentro de eso. A continuación, puede tener io realmente simple de la manera que desee y con cualquier implementación que desee, cualquier formato que desee, etc. (generalmente desea que los flotadores siempre sean una forma, por ejemplo, no formatearlos de 800 maneras sin ninguna razón, por lo que poner en formatear con cada llamada es una broma).

    Así que todo lo que escribo es algo como dout+" Esto es más sano que "+ cPlusPlusMethod + " de " + debugIoType+". OMI como mínimo"; dout++;

    Pero puedes tener lo que quieras. Con muchos archivos es sorprendente lo mucho que esto mejora el tiempo de compilación, también.

    Además, no hay nada de malo en mezclar C y C++, solo debe hacerse jusdiciously y si está utilizando las cosas que causan los problemas con el uso de C en primer lugar, es seguro para digamos que lo menos de tus preocupaciones es el problema de mezclar C y C++.

     1
    Author: Charles Eli Cheese,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2009-12-18 06:31:44

    Mezclar C++ y C iométodos fue recomendado en contra por mis libros de C++, FYI. Estoy bastante seguro de que las funciones de C pisotean el estado esperado / mantenido por C++.

     0
    Author: Paul Nathan,
    Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
    2009-12-17 21:08:04