c++ getline iostream - Perché leggere le righe da stdin è molto più lento in C ++ rispetto a Python?





5 Answers

Solo per curiosità ho dato un'occhiata a quello che succede sotto il cofano, e ho usato dtruss/strace su ogni test.

C ++

./a.out < in
Saw 6512403 lines in 8 seconds.  Crunch speed: 814050

syscalls sudo dtruss -c ./a.out < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            6
pread                                           8
mprotect                                       17
mmap                                           22
stat64                                         30
read_nocancel                               25958

Pitone

./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402

syscalls sudo dtruss -c ./a.py < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            5
pread                                           8
mprotect                                       17
mmap                                           21
stat64                                         29
how use comando

Volevo confrontare le righe di lettura di input di stringa da stdin usando Python e C ++ ed ero scioccato nel vedere il mio codice C ++ eseguire un ordine di grandezza più lento del codice Python equivalente. Dato che il mio C ++ è arrugginito e non sono ancora un esperto Pythonista, ti prego di dirmi se sto facendo qualcosa di sbagliato o se sto fraintendendo qualcosa.

(Risposta TLDR: includi la frase: cin.sync_with_stdio(false) o usa invece fgets .

Risultati TLDR: scorri fino in fondo alla mia domanda e guarda la tabella.)

Codice C ++:

#include <iostream>
#include <time.h>

using namespace std;

int main() {
    string input_line;
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    while (cin) {
        getline(cin, input_line);
        if (!cin.eof())
            line_count++;
    };

    sec = (int) time(NULL) - start;
    cerr << "Read " << line_count << " lines in " << sec << " seconds.";
    if (sec > 0) {
        lps = line_count / sec;
        cerr << " LPS: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

// Compiled with:
// g++ -O3 -o readline_test_cpp foo.cpp

Equivalente Python:

#!/usr/bin/env python
import time
import sys

count = 0
start = time.time()

for line in  sys.stdin:
    count += 1

delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
       lines_per_sec))

Ecco i miei risultati:

$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889

$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000

Dovrei notare che ho provato questo sia con Mac OS X v10.6.8 (Snow Leopard) e Linux 2.6.32 (Red Hat Linux 6.2). Il primo è un MacBook Pro, e quest'ultimo è un server molto robusto, non che questo sia troppo pertinente.

$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP:   Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in  1 seconds. LPS: 5570000

Piccolo punto di riferimento e riepilogo

Per completezza, ho pensato di aggiornare la velocità di lettura per lo stesso file sulla stessa scatola con il codice C ++ originale (sincronizzato). Ancora una volta, questo è per un file di linea 100M su un disco veloce. Ecco il confronto, con diverse soluzioni / approcci:

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714
wc (not fair comparison)  54,644,808



Ho riprodotto il risultato originale sul mio computer usando g ++ su un Mac.

Aggiungendo le seguenti dichiarazioni alla versione C ++ poco prima che il ciclo while lo porti in linea con la versione di Python :

std::ios_base::sync_with_stdio(false);
char buffer[1048576];
std::cin.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

sync_with_stdio ha migliorato la velocità a 2 secondi e l'impostazione di un buffer più grande lo ha ridotto a 1 secondo.




A proposito, il motivo per cui il conteggio delle righe per la versione C ++ è maggiore del conteggio per la versione di Python è che il flag di eof viene impostato solo quando si tenta di leggere oltre eof. Quindi il ciclo corretto sarebbe:

while (cin) {
    getline(cin, input_line);

    if (!cin.eof())
        line_count++;
};



Il seguente codice è stato più veloce per me rispetto all'altro codice pubblicato finora: (Visual Studio 2013, file a 64 bit, 500 MB con lunghezza della linea uniformemente in [0, 1000)).

const int buffer_size = 500 * 1024;  // Too large/small buffer is not good.
std::vector<char> buffer(buffer_size);
int size;
while ((size = fread(buffer.data(), sizeof(char), buffer_size, stdin)) > 0) {
    line_count += count_if(buffer.begin(), buffer.begin() + size, [](char ch) { return ch == '\n'; });
}

Batte tutti i miei tentativi Python di più di un fattore 2.




Bene, vedo che nella tua seconda soluzione sei passato da cin a scanf , che è stato il primo suggerimento che stavo per farti (cin è sloooooooooooow). Ora, se si passa da scanf a fgets , si noterà un ulteriore aumento delle prestazioni: fgets è la funzione C ++ più veloce per l'input di stringhe.

A proposito, non sapevo cosa fosse la sincronizzazione, bello. Ma dovresti comunque provare fgets .






Related