VS 2005: Langsame Konsolenausgabe



  • Hallo,

    mir ist gerade aufgefallen, dass die Konsolenausgabe beim VS 2005 extrem langsam ist. Zumindest wenn man std::cout verwendet.

    #include <iostream>
    #include <stdio.h>
    #include "timer.h"
    using namespace std;
    int main() {
    	ios::sync_with_stdio(false);
    	CTimer t;
    	t.Start();
    	for (int i = 0; i != 100000; ++i) {
    #ifdef USE_PRINTF
    		printf("%d ", i);
    #else
    		cout << i << " ";
    #endif
    		if ( ((i+1) % 10) == 0 ) {
    #ifdef USE_PRINTF
    			printf("\n");
    #else
    			cout << "\n";
    #endif
    		}
    	}
    	t.Stop();
    #ifdef USE_PRINTF
    	printf("Time: %s\n", t.Print().c_str());
    	fflush(stdout);
    #else
    	cout << "Time: " << t.Print() << endl;
    #endif
    }
    

    Ergebnis:
    a) Cout : 7.109
    b) Printf: 1.484

    Wenn ich die Ausgabe in eine Datei umleite erhalte ich:
    Cout: 0.203
    Printf: 0.078

    Das gleiche Programm übersetzt mit einem gcc 3.4.2 (mingw-special) liefert:
    Cout-MGW : 0.969 (Redirected: 0.046)
    Printf-MGW: 1.156 (Redirected: 0.046)

    Gibt es da einen bekannten Grund, warum die Ausgabe beim VC so lahm ist? Oder muss man im Zweifelsfall auf printf umsteigen?

    Danke im Voraus.



  • MS101 schrieb:

    Gibt es da einen bekannten Grund, warum die Ausgabe beim VC so lahm ist? Oder muss man im Zweifelsfall auf printf umsteigen?

    Schlechte Implementierung.

    std::cout kann zwar theoretisch schneller als printf sein (Weil keine Typanalyse erfolgen muss...), in der Praxis gibt es aber wenige Umsetzung die eben dies sind.

    Wobei ich auch sagen muss, das bei allen Programmen die ich bislang geschrieben habe sich cout niemals als der Flaschenhals erwiesen hat.



  • asc schrieb:

    MS101 schrieb:

    Gibt es da einen bekannten Grund, warum die Ausgabe beim VC so lahm ist? Oder muss man im Zweifelsfall auf printf umsteigen?

    Schlechte Implementierung.

    Hm, das hatte ich befürchtet. Jetzt stellt sich nur noch die Frage, ob es die Dinkumware-Lib ist, die in diesem Punkt schlecht implementiert ist oder etwas "darunter".

    Wobei ich auch sagen muss, das bei allen Programmen die ich bislang geschrieben habe sich cout niemals als der Flaschenhals erwiesen hat.

    Das kommt wohl in der Tat selten vor, allerdings ist mir das Problem bei einem realen Programm aufgefallen. Insofern hat die Frage schon einen praktischen Hintergrund.



  • MS101 schrieb:

    Hm, das hatte ich befürchtet. Jetzt stellt sich nur noch die Frage, ob es die Dinkumware-Lib ist, die in diesem Punkt schlecht implementiert ist oder etwas "darunter".

    Zumindestens ist es nicht der übliche Verdächtige: Die "Secure"-Impementierung.

    #define _SECURE_SCL 0

    Bringt bei vielen Bereichen in der VC-STL einen Performanceschub, aber iostream und VC waren schon immer lahm... Leider finde ich die Artikel zum Vergleich nicht mehr.



  • Ist es auch ein Release-Build?



  • Bulli schrieb:

    Ist es auch ein Release-Build?

    Selbstverständlich. Sogar ein angepasster, der noch weiter auf Performanz getrimmt ist. ascs _SECURE_SCL=0 ist so z.B. auch mit dabei.



  • Hallo,
    nur für den Fall, dass es noch jemand anderen interessiert: Problem gelöst!
    Im Gegensatz zu printf (und damit stdout), ist cout beim VC per default ungepuffert1. D.h. jedes Zeichen wird einzeln auf die Konsole geschrieben. Das ist natürlich teuer.

    Ein simples:

    char buffer[4096];
    cout.rdbuf()->pubsetbuf(buffer, 4096);
    

    am Anfang des Codes und schwupps benötigt auch cout nur noch 1 Sekunde für das oben gezeigte Testprogramm.

    1: Der stdout-Stream startet auch ungepuffert. Beim ersten Schreibzugriff über z.B. printf() wird dann aber ein Puffer angelegt und für diesen und jeden weiteren Schreibvorgang verwendet.



  • Oh, das ist jetzt aber sehr interessant! Wie hast du das rausgefunden bzw. gibts dafür eine offizielle Quelle?



  • Nochmal zum Thema buffered/unbuffered. Per Default sind sowohl stdout als auch cout beim VC ungepuffert. Ist auch kein Wunder, da sie den selben Streampuffer (aka FILE*) benutzen (d.h. man kann auch einfach setbuf(stdout, buffer) statt cout.rdbuf()->pubsetbuf() benutzen). printf() und co benutzen aber einen kleinen Trick:

    char* buf = 0;
    printf(const char* format, ...) {
      lockStream();
      bool dismantle = false;
      if (isTTY(stdout) && stdout.buf == 0) {
        if (buf == 0) buf = allocateBuffer();
        // temporarily buffer the stream
        stdout.buf = buf;
        dismantle = true;
      }
      processAndWrite(format, stdout);
      if (dismantle) {
        fflush(stdout);
        stdout.buf = 0;
      }
    }
    

    D.h. selbst wenn kein Puffer verwendet wird (Default: stdout.buf == 0), wird für eine einzelne Ausgabe temporär ein Puffer genutzt. Eine Ausgabe wird also als Ganzes auf die Konsole geschrieben. Bei cout wird dieser Trick nicht genutzt. Wenn hier stdout.buf == 0 ist, wird jede Ausgabe Zeichen für Zeichen auf die Konsole geschrieben.



  • Bulli schrieb:

    Oh, das ist jetzt aber sehr interessant! Wie hast du das rausgefunden bzw. gibts dafür eine offizielle Quelle?

    Quellcode studiert 😉


Anmelden zum Antworten