Perché la lettura di righe da stdin è molto più lenta in C ++ di Python?


1840

Volevo confrontare le linee di lettura dell'input di stringa dallo stdin usando Python e C ++ e sono rimasto scioccato nel vedere il mio codice C ++ eseguire un ordine di grandezza più lento dell'equivalente codice Python. Poiché il mio C ++ è arrugginito e non sono ancora un esperto Pythonista, per favore dimmi se sto facendo qualcosa di sbagliato o se fraintendo qualcosa.


(Risposta TLDR: includi la dichiarazione: cin.sync_with_stdio(false)o usa semplicemente fgetsinvece.

Risultati TLDR: scorrere fino in fondo alla mia domanda e guardare 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

Python equivalente:

#!/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 su Mac OS X v10.6.8 (Snow Leopard) sia su Linux 2.6.32 (Red Hat Linux 6.2). Il primo è un MacBook Pro e il secondo è un server molto robusto, non che 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 addendum benchmark 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

14
Hai eseguito i test più volte? Forse c'è un problema con la cache del disco.
Vaughn Cato,

9
@JJC: vedo due possibilità (supponendo che tu abbia rimosso il problema di memorizzazione nella cache suggerito da David): 1) le <iostream>prestazioni fanno schifo. Non è la prima volta che succede. 2) Python è abbastanza intelligente da non copiare i dati nel ciclo for perché non li usi. È possibile ripetere il test cercando di utilizzare scanfe a char[]. In alternativa, puoi provare a riscrivere il ciclo in modo che qualcosa venga fatto con la stringa (ad esempio, mantieni la quinta lettera e concatenala in un risultato).
JN,

15
Il problema è la sincronizzazione con stdio - vedi la mia risposta.
Vaughn Cato,

19
Dal momento che nessuno sembra aver menzionato il motivo per cui ottieni una linea in più con C ++: non testare contro cin.eof()!! Metti la getlinechiamata nell'istruzione 'if`.
Xeo,

21
wc -lè veloce perché legge il flusso più di una riga alla volta (potrebbe essere una fread(stdin)/memchr('\n')combinazione). I risultati di Python sono nello stesso ordine di grandezza, ad es.wc-l.py
jfs,

Risposte:


1644

Per impostazione predefinita, cinè sincronizzato con stdio, il che gli fa evitare qualsiasi buffer di input. Se lo aggiungi nella parte superiore del tuo principale, dovresti vedere prestazioni molto migliori:

std::ios_base::sync_with_stdio(false);

Normalmente, quando un flusso di input viene bufferizzato, invece di leggere un carattere alla volta, il flusso verrà letto in blocchi più grandi. Ciò riduce il numero di chiamate di sistema, che sono in genere relativamente costose. Tuttavia, poiché i FILE*basati stdioe iostreamsspesso hanno implementazioni separate e quindi buffer separati, ciò potrebbe causare un problema se entrambi fossero usati insieme. Per esempio:

int myvalue1;
cin >> myvalue1;
int myvalue2;
scanf("%d",&myvalue2);

Se fosse stato letto più input cindi quanto effettivamente necessario, il secondo valore intero non sarebbe disponibile per la scanffunzione, che ha il suo buffer indipendente. Ciò porterebbe a risultati inaspettati.

Per evitare ciò, per impostazione predefinita, i flussi sono sincronizzati con stdio. Un modo comune per raggiungere questo obiettivo è cinleggere ogni personaggio uno alla volta, se necessario, utilizzando le stdiofunzioni. Sfortunatamente, questo introduce molte spese generali. Per piccole quantità di input, questo non è un grosso problema, ma quando stai leggendo milioni di righe, la penalità delle prestazioni è significativa.

Fortunatamente, i progettisti della biblioteca hanno deciso che dovresti anche essere in grado di disabilitare questa funzione per migliorare le prestazioni se sapessi cosa stavi facendo, quindi hanno fornito il sync_with_stdiometodo.


142
Questo dovrebbe essere al top. È quasi certamente corretto. La risposta non può consistere nel sostituire la lettura con una fscanfchiamata, perché semplicemente non funziona tanto quanto Python. Python deve allocare memoria per la stringa, possibilmente più volte poiché l'allocazione esistente è considerata inadeguata, esattamente come l'approccio C ++ con std::string. Questo compito è quasi certamente legato all'I / O e c'è troppo FUD che va in giro per il costo della creazione di std::stringoggetti in C ++ o dell'uso <iostream>in sé e per sé.
Karl Knechtel,

51
Sì, l'aggiunta di questa riga immediatamente sopra il mio ciclo while originale ha velocizzato il codice per superare anche Python. Sto per pubblicare i risultati come modifica finale. Grazie ancora!
JJC

6
Sì, questo vale anche per cout, cerr e clog.
Vaughn Cato,

2
Per rendere più veloci cout, cin, cerr e clog, fallo in questo modo std :: ios_base :: sync_with_stdio (false);
01100110

56
Si noti che sync_with_stdio()è una funzione membro statica e una chiamata a questa funzione su qualsiasi oggetto stream (ad es. cin) Attiva o disattiva la sincronizzazione per tutti gli oggetti iostream standard.
John Zwinck,

171

Solo per curiosità ho dato un'occhiata a cosa succede sotto il cofano e ho usato dtruss / strace in ogni test.

C ++

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

chiamate di sistema 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

chiamate di sistema sudo dtruss -c ./a.py < in

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

159

Sono qui da qualche anno, ma:

In "Modifica 4/5/6" del post originale, stai usando la costruzione:

$ /usr/bin/time cat big_file | program_to_benchmark

Questo è sbagliato in un paio di modi diversi:

  1. In realtà stai sincronizzando l'esecuzione cat, non il tuo benchmark. L'utilizzo della CPU "user" e "sys" visualizzato timeè quello di cat, non il tuo programma di riferimento. Ancora peggio, anche il tempo "reale" non è necessariamente preciso. A seconda dell'implementazione di cate delle pipeline nel sistema operativo locale, è possibile che catscriva un buffer gigante finale ed esca molto prima che il processo di lettura finisca il suo lavoro.

  2. L'uso di catnon è necessario e di fatto controproducente; stai aggiungendo parti in movimento. Se tu fossi su un sistema sufficientemente vecchio (cioè con una singola CPU e - in alcune generazioni di computer - I / O più veloce della CPU) - il semplice fatto che catera in esecuzione potrebbe sostanzialmente colorare i risultati. Sei inoltre soggetto a qualunque operazione di buffering di input e output e di altre elaborazioni cat. (Questo probabilmente ti farebbe guadagnare un premio "Uso inutile del gatto" se fossi Randal Schwartz.

Una costruzione migliore sarebbe:

$ /usr/bin/time program_to_benchmark < big_file

In questa affermazione è la shell che apre big_file, passandolo al tuo programma (bene, in realtà a timecui quindi esegue il tuo programma come sottoprocesso) come descrittore di file già aperto. Il 100% della lettura dei file è strettamente responsabilità del programma che stai cercando di confrontare. Questo ti dà una vera lettura delle sue prestazioni senza complicazioni spurie.

Citerò due possibili "correzioni" che potrebbero anche essere prese in considerazione (ma le "numererò" in modo diverso poiché queste non sono cose che erano sbagliate nel post originale):

R. Potresti 'risolvere' questo tempismo solo il tuo programma:

$ cat big_file | /usr/bin/time program_to_benchmark

B. o temporizzando l'intero gasdotto:

$ /usr/bin/time sh -c 'cat big_file | program_to_benchmark'

Questi sono errati per gli stessi motivi del n. 2: stanno ancora usando catinutilmente. Li cito per alcuni motivi:

  • sono più "naturali" per le persone che non sono del tutto a proprio agio con le strutture di reindirizzamento I / O della shell POSIX

  • ci possono essere casi in cui cat è necessaria (ad esempio: il file da leggere richiede una sorta di privilegio di accesso, e non si vuole concedere questo privilegio per il programma da benchmark: sudo cat /dev/sda | /usr/bin/time my_compression_test --no-output)

  • in pratica , su macchine moderne, l'aggiunta catnella pipeline probabilmente non ha conseguenze reali.

Ma dico l'ultima cosa con qualche esitazione. Se esaminiamo l'ultimo risultato in "Modifica 5" -

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU ...

- ciò afferma che catdurante il test ha consumato il 74% della CPU; e infatti 1,34 / 1,83 è di circa il 74%. Forse una corsa di:

$ /usr/bin/time wc -l < temp_big_file

avrebbe impiegato solo i rimanenti .49 secondi! Probabilmente no: catqui dovevano pagare le read()chiamate di sistema (o equivalenti) che trasferivano il file da "disco" (in realtà buffer cache), così come il pipe scrive per consegnarle wc. Il test corretto avrebbe comunque dovuto fare quelliread() chiamate; solo le chiamate write-to-pipe e read-from-pipe sarebbero state salvate e quelle dovrebbero essere piuttosto economiche.

Eppure, prevedo si sarebbe in grado di misurare la differenza tra cat file | wc -le wc -l < filee trovare un (percentuale a 2 cifre) differenza notevole. Ognuno dei test più lenti avrà pagato una penalità simile in tempo assoluto; che equivarrebbe comunque a una frazione minore del suo tempo totale maggiore.

In effetti ho fatto alcuni test rapidi con un file da 1,5 gigabyte di immondizia, su un sistema Linux 3.13 (Ubuntu 14.04), ottenendo questi risultati (questi sono in realtà i risultati "migliori di 3"; dopo aver innescato la cache, ovviamente):

$ time wc -l < /tmp/junk
real 0.280s user 0.156s sys 0.124s (total cpu 0.280s)
$ time cat /tmp/junk | wc -l
real 0.407s user 0.157s sys 0.618s (total cpu 0.775s)
$ time sh -c 'cat /tmp/junk | wc -l'
real 0.411s user 0.118s sys 0.660s (total cpu 0.778s)

Si noti che i due risultati della pipeline sostengono di aver impiegato più tempo della CPU (utente + sys) rispetto al tempo reale dell'orologio a parete. Questo perché sto usando il comando incorporato 'time' della shell (bash), che è a conoscenza della pipeline; e mi trovo su una macchina multi-core in cui processi separati in una pipeline possono utilizzare core separati, accumulando il tempo della CPU più velocemente del tempo reale. Utilizzando /usr/bin/timeVedo un tempo di CPU inferiore rispetto al tempo reale, a dimostrazione del fatto che può solo passare il tempo al singolo elemento della pipeline ad esso passato sulla sua riga di comando. Inoltre, l'output della shell fornisce millisecondi mentre /usr/bin/timedà solo i centesimi di secondo.

Quindi a livello di efficienza di wc -l, ilcat differenza è enorme: 409/283 = 1.453 o 45.3% in più in tempo reale e 775/280 = 2.768, o un enorme 177% in più di CPU utilizzata! Sulla mia casella di prova casuale era lì al momento.

Dovrei aggiungere che esiste almeno un'altra differenza significativa tra questi stili di test e non posso dire se si tratti di un vantaggio o di un difetto; devi decidere tu stesso:

Quando corri cat big_file | /usr/bin/time my_program, il tuo programma riceve input da una pipe, esattamente al ritmo inviato da cat, e in blocchi non più grandi di quelli scritti da cat.

Quando si esegue /usr/bin/time my_program < big_file, il programma riceve un descrittore di file aperto sul file effettivo. Il tuo programma - o in molti casi le librerie I / O della lingua in cui è stato scritto - può intraprendere azioni diverse quando viene presentato con un descrittore di file che fa riferimento a un file normale. Può utilizzare mmap(2)per mappare il file di input nel suo spazio di indirizzi, anziché utilizzare read(2)chiamate di sistema esplicite . Queste differenze potrebbero avere un effetto molto maggiore sui risultati del benchmark rispetto al piccolo costo di gestione dicat binario.

Naturalmente è un risultato di riferimento interessante se lo stesso programma si comporta in modo significativamente diverso tra i due casi. Mostra che, in effetti, il programma o le sue librerie I / O stanno facendo qualcosa di interessante, come usare mmap(). Quindi, in pratica, potrebbe essere utile eseguire i benchmark in entrambi i modi; forse scartando il catrisultato con qualche piccolo fattore per "perdonare" il costo della corsa catstessa.


26
Wow, è stato abbastanza penetrante! Mentre ero consapevole che cat non è necessario per alimentare l'input allo stdin dei programmi e che si preferisce il <reindirizzamento della shell, in genere mi sono bloccato su cat a causa del flusso di dati da sinistra a destra che il precedente metodo conserva visivamente quando ragio sulle condutture. Le differenze di prestazione in questi casi sono risultate trascurabili. Ma apprezzo il fatto che ci stai educando, Bela.
JJC

11
Il reindirizzamento viene analizzato dalla riga di comando della shell in una fase iniziale, che consente di eseguire una di queste, se dà un aspetto più piacevole del flusso da sinistra a destra: $ < big_file time my_program $ time < big_file my_program questo dovrebbe funzionare in qualsiasi shell POSIX (cioè non `csh `e non sono sicuro di exotica come` rc`:)
Bela Lubkin

6
Ancora una volta, a parte la differenza forse forse poco interessante di prestazioni incrementali dovuta al binario `cat` in esecuzione allo stesso tempo, stai rinunciando alla possibilità che il programma in prova sia in grado di mmap () il file di input. Ciò potrebbe fare una profonda differenza nei risultati. Questo è vero anche se hai scritto tu stesso i benchmark, nelle varie lingue, usando solo il loro linguaggio "input input from a file". Dipende dal funzionamento dettagliato delle loro varie librerie I / O.
Bela Lubkin

2
Nota a margine: l'integrato di Bash timesta misurando l'intera pipeline anziché il primo programma. time seq 2 | while read; do sleep 1; donestampa 2 sec, /usr/bin/time seq 2 | while read; do sleep 1; donestampa 0 sec.
folkol

1
@folkol - sì, << Notare che le due pipeline risultano [mostrano] più CPU [di] tempo reale [usando] (Bash) il comando incorporato 'time'; ... / usr / bin / time ... può solo temporizzare il singolo elemento della pipeline passato ad esso sulla sua riga di comando. >> '
Bela Lubkin,

90

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

L'aggiunta delle seguenti istruzioni alla versione C ++ appena prima del whileciclo lo porta 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.


5
È possibile provare diverse dimensioni del buffer per ottenere informazioni più utili. Sospetto che vedrai rendimenti in rapido calo.
Karl Knechtel,

8
Ero troppo frettoloso nella mia risposta; l'impostazione della dimensione del buffer su un valore diverso da quello predefinito non ha prodotto una differenza apprezzabile.
Karunski,

109
Eviterei anche di impostare un buffer da 1 MB nello stack. Può portare allo stackoverflow (anche se immagino sia un buon posto per discuterne!)
Matthieu M.

11
Matthieu, Mac utilizza uno stack di processo da 8 MB per impostazione predefinita. Linux usa 4 MB per thread di default, IIRC. 1 MB non è un grosso problema per un programma che trasforma l'input con una profondità dello stack relativamente bassa. Ancora più importante, però, std :: cin eliminerà lo stack se il buffer non rientra nell'ambito.
SEK

22
@SEK La dimensione predefinita dello stack di Windows è 1 MB.
Étienne,

39

getline, operatori di streaming, scanf , può essere utile se non ti interessa il tempo di caricamento dei file o se stai caricando piccoli file di testo. Ma, se le prestazioni sono qualcosa di cui ti preoccupi, dovresti semplicemente bufferizzare l'intero file in memoria (supponendo che si adatta).

Ecco un esempio:

//open file in binary mode
std::fstream file( filename, std::ios::in|::std::ios::binary );
if( !file ) return NULL;

//read the size...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);

//read into memory buffer, then close it.
char *filebuf = new char[length+1];
file.read(filebuf, length);
filebuf[length] = '\0'; //make it null-terminated
file.close();

Se lo desideri, puoi avvolgere uno stream attorno a quel buffer per un accesso più comodo come questo:

std::istrstream header(&filebuf[0], length);

Inoltre, se hai il controllo del file, prendi in considerazione l'utilizzo di un formato di dati binari piatto anziché di testo. È più affidabile leggere e scrivere perché non devi affrontare tutte le ambiguità degli spazi bianchi. È anche più piccolo e molto più veloce da analizzare.


20

Il seguente codice è stato più veloce per me rispetto all'altro codice pubblicato qui finora: (Visual Studio 2013, 64 bit, file da 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 di Python di più di un fattore 2.


Puoi ottenere anche più veloce di così con un minuscolo programma personalizzato ma completamente semplice che trasforma in modo iterativo le readsyscalls senza buffer in un buffer statico di lunghezza BUFSIZEo tramite le equivalenti mmapsyscalls corrispondenti , e quindi scorre attraverso quel buffer contando le nuove righe à la for (char *cp = buf; *cp; cp++) count += *cp == "\n". Dovrai sintonizzarti BUFSIZEper il tuo sistema, tuttavia, quale stdio avrà già fatto per te. Ma quel forloop dovrebbe essere compilato in istruzioni di linguaggio assembler incredibilmente veloci e urlanti per l'hardware della tua scatola.
tchrist,

3
count_if e un lambda si compilano anche in "assemblatore incredibilmente veloce urlando".
Petter,

17

A proposito, il motivo per cui il conteggio delle righe per la versione C ++ è maggiore del conteggio per la versione Python è che il flag 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++;
};

70
Il ciclo davvero corretto sarebbe: while (getline(cin, input_line)) line_count++;
Jonathan Wakely

2
@JonathanSicuramente so che sono in ritardo, ma usa ++line_count;e non line_count++;.
dice Val Reinstate Monica l'

7
@val se questo fa la differenza il tuo compilatore ha un bug. La variabile è a longe il compilatore è abbastanza in grado di dire che il risultato dell'incremento non viene utilizzato. Se non genera codice identico per postincremento e preincremento, è rotto.
Jonathan Wakely,

2
In effetti, qualsiasi compilatore decente sarà in grado di rilevare un abuso post-incremento e sostituirlo con un pre-incremento invece, ma i compilatori non sono tenuti a farlo . Quindi no, non è rotto anche se il compilatore non esegue la sostituzione. Inoltre, scrivere ++line_count;invece di line_count++;non farebbe male :)
Fareanor

1
@valsaysReinstateMonica In questo esempio specifico, perché uno dei due dovrebbe essere preferito? Il risultato non viene utilizzato qui in entrambi i modi, quindi verrà letto dopo il while, giusto? Avrebbe importanza se ci fosse una sorta di errore e tu volessi assicurarti che line_countfosse corretto? Sto solo indovinando ma non capisco perché sia ​​importante.
TankorSmash,

14

Nel tuo secondo esempio (con scanf ()) il motivo per cui questo è ancora più lento potrebbe essere perché scanf ("% s") analizza la stringa e cerca qualsiasi carattere spaziale (spazio, tabulazione, nuova riga).

Inoltre, sì, CPython esegue la memorizzazione nella cache per evitare letture del disco rigido.


12

Un primo elemento di una risposta: <iostream>è lento. Dannatamente lento. Ottengo un enorme aumento delle prestazioni con scanfcome nel seguito, ma è ancora due volte più lento di Python.

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

using namespace std;

int main() {
    char buffer[10000];
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    int read = 1;
    while(read > 0) {
        read = scanf("%s", buffer);
        line_count++;
    };
    sec = (int) time(NULL) - start;
    line_count--;
    cerr << "Saw " << line_count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = line_count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } 
    else
        cerr << endl;
    return 0;
}

Non ho visto questo post fino a quando non ho effettuato la mia terza modifica, ma grazie ancora per il tuo suggerimento. Stranamente, non c'è nessun hit 2x per me vs Python ora con la linea scanf in edit3 sopra. Sto usando 2.7, a proposito.
JJC

10
Dopo aver corretto la versione c ++, questa versione stdio è sostanzialmente più lenta della versione iostreams c ++ sul mio computer. (3 secondi contro 1 secondo)
karunski,

10

Bene, vedo che nella vostra seconda soluzione si è passati da cina scanf, che è stato il primo suggerimento che stavo per farvi (cin è sloooooooooooow). Ora, se passi da scanfa fgets, vedresti un altro aumento delle prestazioni:fgets è la funzione C ++ più veloce per l'input di stringhe.

A proposito, non sapevo di quella cosa di sincronizzazione, bello. Ma dovresti ancora provare fgets.


2
Tranne fgetssarà errato (in termini di conteggio delle linee e in termini di divisione delle linee tra i loop se in realtà è necessario utilizzarle) per linee sufficientemente grandi, senza controlli aggiuntivi per linee incomplete (e tentare di compensare comporta l'assegnazione di buffer inutilmente grandi , dove std::getlinegestisce la riallocazione in modo che corrisponda perfettamente all'input effettivo). Veloce e sbagliato è facile, ma quasi sempre ne vale la pena usare "leggermente più lento, ma corretto", che spegnere sync_with_stdioti dà .
ShadowRanger
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.