Come ottenere IOStream per ottenere prestazioni migliori?

La maggior parte degli utenti C ++ che hanno imparato C preferiscono usare la famiglia di funzioni printf / scanf anche quando codificano in C ++.

Anche se ammetto di trovare l’interfaccia migliore (in particolare il formato e la localizzazione POSIX), sembra che una preoccupazione travolgente sia rappresentata dalle prestazioni.

Dando un’occhiata a questa domanda:

Come posso velocizzare la lettura riga per riga di un file

Sembra che la risposta migliore sia usare fscanf e che il C ++ ifstream sia costantemente 2-3 volte più lento.

Ho pensato che sarebbe bello se potessimo compilare un repository di “suggerimenti” per migliorare le prestazioni di IOStreams, cosa funziona, cosa no.

Punti da considerare

  • buffering ( rdbuf()->pubsetbuf(buffer, size) )
  • sincronizzazione ( std::ios_base::sync_with_stdio )
  • gestione delle impostazioni locali (potremmo usare un locale ridotto o rimuoverlo del tutto?)

Naturalmente, altri approcci sono ben accetti.

Nota: una “nuova” implementazione, di Dietmar Kuhl, è stata menzionata, ma non sono stato in grado di trovare molti dettagli a riguardo. I riferimenti precedenti sembrano essere collegamenti morti.

Ecco cosa ho raccolto finora:

Buffering :

Se di default il buffer è molto piccolo, aumentare la dimensione del buffer può sicuramente migliorare le prestazioni:

  • riduce il numero di colpi HDD
  • riduce il numero di chiamate di sistema

Il buffer può essere impostato accedendo all’implementazione di streambuf sottostante.

 char Buffer[N]; std::ifstream file("file.txt"); file.rdbuf()->pubsetbuf(Buffer, N); // the pointer reader by rdbuf is guaranteed // to be non-null after successful constructor 

Avviso di cortesia di @iavr: secondo cppreference è meglio chiamare pubsetbuf prima di aprire il file. Diverse implementazioni di libreria standard hanno altrimenti comportamenti diversi.

Gestione locale:

Le impostazioni locali possono eseguire conversioni di caratteri, filtri e trucchi più intelligenti in cui sono coinvolti numeri o date. Attraversano un sistema complesso di invio dinamico e chiamate virtuali, quindi rimuoverli può aiutare a ridurre il tiro di rigore.

L’impostazione predefinita locale C è pensata per non eseguire alcuna conversione oltre ad essere uniforms su tutte le macchine. È un buon valore predefinito da usare.

Sincronizzazione:

Non sono riuscito a vedere alcun miglioramento delle prestazioni utilizzando questa funzione.

È ansible accedere a un’impostazione globale (membro statico di std::ios_base ) utilizzando la funzione statica sync_with_stdio .

misure:

Giocando con questo, ho giocato con un semplice programma, compilato usando gcc 3.4.2 su SUSE 10p3 con -O2 .

C: 7.76532e + 06
C ++: 1.0874e + 07

Che rappresenta un rallentamento di circa il 20% … per il codice predefinito. Effettivamente la manomissione del buffer (in C o C ++) o dei parametri di sincronizzazione (C ++) non ha prodotto alcun miglioramento.

Risultati di altri:

@Irfy su g ++ 4.7.2-2ubuntu1, -O3, virtualizzato Ubuntu 11.10, 3.5.0-25-generico, x86_64, abbastanza ram / cpu, 196 MB di diverse corse “find / >> largefile.txt”

C: 634572 C ++: 473222

C + + 25% più veloce

@Matteo Italia su g ++ 4.4.5, -O3, Ubuntu Linux 10.10 x86_64 con un file casuale da 180 MB

C: 910390
C ++: 776016

C ++ 17% più veloce

@Bogatyr su g ++ i686-apple-darwin10-g ++ – 4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664), mac mini, ram da 4 GB, inattivo eccetto questo test con un file di dati da 168 MB

C: 4.34151e + 06
C ++: 9.14476e + 06

C ++ 111% più lento

@Asu su clang ++ 3.8.0-2ubuntu4, Kubuntu 16.04 Linux 4.8-rc3, 8GB ram, i5 Haswell, Crucial SSD, 88MB datafile (archivio tar.xz)

C: 270895 C ++: 162799

C + + 66% più veloce

Quindi la risposta è: è una questione di qualità di implementazione, e in realtà dipende dalla piattaforma: /

Il codice completo qui per chi è interessato al benchmarking:

 #include  #include  #include  #include  #include  #include  template  double benchmark(Func f, size_t iterations) { f(); timeval a, b; gettimeofday(&a, 0); for (; iterations --> 0;) { f(); } gettimeofday(&b, 0); return (b.tv_sec * (unsigned int)1e6 + b.tv_usec) - (a.tv_sec * (unsigned int)1e6 + a.tv_usec); } struct CRead { CRead(char const* filename): _filename(filename) {} void operator()() { FILE* file = fopen(_filename, "r"); int count = 0; while ( fscanf(file,"%s", _buffer) == 1 ) { ++count; } fclose(file); } char const* _filename; char _buffer[1024]; }; struct CppRead { CppRead(char const* filename): _filename(filename), _buffer() {} enum { BufferSize = 16184 }; void operator()() { std::ifstream file(_filename, std::ifstream::in); // comment to remove extended buffer file.rdbuf()->pubsetbuf(_buffer, BufferSize); int count = 0; std::string s; while ( file >> s ) { ++count; } } char const* _filename; char _buffer[BufferSize]; }; int main(int argc, char* argv[]) { size_t iterations = 1; if (argc > 1) { iterations = atoi(argv[1]); } char const* oldLocale = setlocale(LC_ALL,"C"); if (strcmp(oldLocale, "C") != 0) { std::cout << "Replaced old locale '" << oldLocale << "' by 'C'\n"; } char const* filename = "largefile.txt"; CRead cread(filename); CppRead cppread(filename); // comment to use the default setting bool oldSyncSetting = std::ios_base::sync_with_stdio(false); double ctime = benchmark(cread, iterations); double cpptime = benchmark(cppread, iterations); // comment if oldSyncSetting's declaration is commented std::ios_base::sync_with_stdio(oldSyncSetting); std::cout << "C : " << ctime << "\n" "C++: " << cpptime << "\n"; return 0; } 

Altri due miglioramenti:

Issue std::cin.tie(nullptr); prima di pesanti input / output.

Citando http://en.cppreference.com/w/cpp/io/cin :

Una volta che std :: cin è stato costruito, std :: cin.tie () restituisce & std :: cout, e allo stesso modo, std :: wcin.tie () restituisce & std :: wcout. Ciò significa che qualsiasi operazione di input formattata su std :: cin forza una chiamata a std :: cout.flush () se qualche carattere è in sospeso per l’output.

Puoi evitare di svuotare il buffer sciogliendo std::cin da std::cout . Questo è rilevante con più chiamate miste a std::cin e std::cout . Si noti che chiamando std::cin.tie(std::nullptr); rende il programma inadatto per essere eseguito intertriggersmente dall’utente, poiché l’output potrebbe essere ritardato.

Riferimento pertinente:

File test1.cpp :

 #include  using namespace std; int main() { ios_base::sync_with_stdio(false); int i; while(cin >> i) cout << i << '\n'; } 

File test2.cpp :

 #include  using namespace std; int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int i; while(cin >> i) cout << i << '\n'; cout.flush(); } 

Entrambi compilati da g++ -O2 -std=c++11 . Versione del compilatore: g++ (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4 (sì, lo so, piuttosto vecchio).

Risultati del benchmark:

 [email protected] ~ $ time ./test1 < test.in > test1.in real 0m3.140s user 0m0.581s sys 0m2.560s [email protected] ~ $ time ./test2 < test.in > test2.in real 0m0.234s user 0m0.234s sys 0m0.000s 

( test.in consiste di 1179648 righe ciascuna composta da un solo 5 È 2,4 MB, quindi scusate se non lo pubblicate qui.).

Ricordo di aver risolto un compito algoritmico in cui il giudice online continuava a rifiutare il mio programma senza cin.tie(nullptr) ma lo accettava con cin.tie(nullptr) o printf / scanf invece di cin / cout .

Usa '\n' invece di std::endl .

Citando http://en.cppreference.com/w/cpp/io/manip/endl :

Inserisce un carattere di nuova riga nella sequenza di output os e lo svuota come se chiamasse os.put (os.widen ('\ n')) seguito da os.flush ().

Puoi evitare di svuotare il bufer stampando '\n' posto di endl .

Riferimento pertinente:

File test1.cpp :

 #include  using namespace std; int main() { ios_base::sync_with_stdio(false); for(int i = 0; i < 1179648; ++i) cout << i << endl; } 

File test2.cpp :

 #include  using namespace std; int main() { ios_base::sync_with_stdio(false); for(int i = 0; i < 1179648; ++i) cout << i << '\n'; } 

Entrambi compilati come sopra.

Risultati del benchmark:

 [email protected] ~ $ time ./test1 > test1.in real 0m2.946s user 0m0.404s sys 0m2.543s [email protected] ~ $ time ./test2 > test2.in real 0m0.156s user 0m0.135s sys 0m0.020s 

È interessante notare che i programmatori C preferiscono printf durante la scrittura di C ++, poiché vedo un sacco di codice diverso da C e non da cout e iostream per scrivere l’output.

Gli usi possono spesso migliorare le prestazioni usando direttamente filebuf (Scott Meyers ha menzionato questo in AWL efficace), ma c’è una documentazione relativamente scarsa nell’uso di filebuf diretto e la maggior parte degli sviluppatori preferisce std::getline che è più semplice la maggior parte del tempo.

Per quanto riguarda le impostazioni locali, se crei sfaccettature otterrai spesso prestazioni migliori creando una locale con tutte le sfaccettature, conservandola e inserendola in ogni stream che utilizzi.

Ho visto un altro argomento su questo qui di recente, quindi questo è vicino ad essere un duplicato.