Sto cercando di convertire del codice da Python a C ++ nel tentativo di guadagnare un po 'di velocità e affinare le mie arrugginite abilità C ++. Ieri sono rimasto scioccato quando un'implementazione ingenua della lettura di righe da stdin è stata molto più veloce in Python rispetto a C ++ (vedi questo ). Oggi, ho finalmente capito come dividere una stringa in C ++ con l'unione di delimitatori (semantica simile a split ()) di python, e ora sto sperimentando il deja vu! Il mio codice C ++ impiega molto più tempo per svolgere il lavoro (anche se non un ordine di grandezza in più, come nel caso della lezione di ieri).
Codice Python:
#!/usr/bin/env python
from __future__ import print_function
import time
import sys
count = 0
start_time = time.time()
dummy = None
for line in sys.stdin:
dummy = line.split()
count += 1
delta_sec = int(time.time() - start_time)
print("Python: Saw {0} lines in {1} seconds. ".format(count, delta_sec), end='')
if delta_sec > 0:
lps = int(count/delta_sec)
print(" Crunch Speed: {0}".format(lps))
else:
print('')
Codice C ++:
#include <iostream>
#include <string>
#include <sstream>
#include <time.h>
#include <vector>
using namespace std;
void split1(vector<string> &tokens, const string &str,
const string &delimiters = " ") {
// Skip delimiters at beginning
string::size_type lastPos = str.find_first_not_of(delimiters, 0);
// Find first non-delimiter
string::size_type pos = str.find_first_of(delimiters, lastPos);
while (string::npos != pos || string::npos != lastPos) {
// Found a token, add it to the vector
tokens.push_back(str.substr(lastPos, pos - lastPos));
// Skip delimiters
lastPos = str.find_first_not_of(delimiters, pos);
// Find next non-delimiter
pos = str.find_first_of(delimiters, lastPos);
}
}
void split2(vector<string> &tokens, const string &str, char delim=' ') {
stringstream ss(str); //convert string to stream
string item;
while(getline(ss, item, delim)) {
tokens.push_back(item); //add token to vector
}
}
int main() {
string input_line;
vector<string> spline;
long count = 0;
int sec, lps;
time_t start = time(NULL);
cin.sync_with_stdio(false); //disable synchronous IO
while(cin) {
getline(cin, input_line);
spline.clear(); //empty the vector for the next line to parse
//I'm trying one of the two implementations, per compilation, obviously:
// split1(spline, input_line);
split2(spline, input_line);
count++;
};
count--; //subtract for final over-read
sec = (int) time(NULL) - start;
cerr << "C++ : Saw " << count << " lines in " << sec << " seconds." ;
if (sec > 0) {
lps = count / sec;
cerr << " Crunch speed: " << lps << endl;
} else
cerr << endl;
return 0;
//compiled with: g++ -Wall -O3 -o split1 split_1.cpp
Nota che ho provato due diverse implementazioni divise. Uno (split1) utilizza metodi di stringa per cercare token ed è in grado di unire più token e gestire numerosi token (proviene da qui ). Il secondo (split2) utilizza getline per leggere la stringa come un flusso, non unisce i delimitatori e supporta solo un singolo carattere delimitatore (quello è stato pubblicato da diversi utenti StackOverflow nelle risposte alle domande sulla divisione delle stringhe).
L'ho eseguito più volte in vari ordini. La mia macchina di prova è un Macbook Pro (2011, 8 GB, Quad Core), non che importi molto. Sto testando con un file di testo di 20 milioni di righe con tre colonne separate da spazi, ognuna simile a questa: "foo.bar 127.0.0.1 home.foo.bar"
Risultati:
$ /usr/bin/time cat test_lines_double | ./split.py
15.61 real 0.01 user 0.38 sys
Python: Saw 20000000 lines in 15 seconds. Crunch Speed: 1333333
$ /usr/bin/time cat test_lines_double | ./split1
23.50 real 0.01 user 0.46 sys
C++ : Saw 20000000 lines in 23 seconds. Crunch speed: 869565
$ /usr/bin/time cat test_lines_double | ./split2
44.69 real 0.02 user 0.62 sys
C++ : Saw 20000000 lines in 45 seconds. Crunch speed: 444444
Che cosa sto facendo di sbagliato? Esiste un modo migliore per eseguire la suddivisione delle stringhe in C ++ che non si basi su librerie esterne (ovvero nessun boost), supporti l'unione di sequenze di delimitatori (come la divisione di Python), sia thread-safe (quindi niente strtok) e le cui prestazioni siano almeno alla pari con il pitone?
Modifica 1 / Soluzione parziale ?:
Ho provato a renderlo un confronto più equo facendo in modo che Python resettasse l'elenco fittizio e lo aggiungesse ogni volta, come fa C ++. Questo non è ancora esattamente ciò che sta facendo il codice C ++, ma è un po 'più vicino. Fondamentalmente, il ciclo è ora:
for line in sys.stdin:
dummy = []
dummy += line.split()
count += 1
Le prestazioni di python sono ora più o meno le stesse dell'implementazione C ++ di split1.
/usr/bin/time cat test_lines_double | ./split5.py
22.61 real 0.01 user 0.40 sys
Python: Saw 20000000 lines in 22 seconds. Crunch Speed: 909090
Sono ancora sorpreso dal fatto che, anche se Python è così ottimizzato per l'elaborazione delle stringhe (come suggerito da Matt Joiner), queste implementazioni C ++ non sarebbero più veloci. Se qualcuno ha idee su come farlo in modo ottimale utilizzando C ++, condividi il tuo codice. (Penso che il mio prossimo passo sarà cercare di implementarlo in C puro, anche se non ho intenzione di compromettere la produttività del programmatore per reimplementare il mio progetto complessivo in C, quindi questo sarà solo un esperimento per la velocità di divisione delle stringhe.)
Grazie a tutti per il vostro aiuto.
Modifica / soluzione finale:
Si prega di vedere la risposta accettata di Alf. Poiché python si occupa delle stringhe esclusivamente per riferimento e le stringhe STL vengono spesso copiate, le prestazioni sono migliori con le implementazioni vanilla python. Per confronto, ho compilato ed eseguito i miei dati tramite il codice di Alf, ed ecco le prestazioni sulla stessa macchina di tutte le altre esecuzioni, essenzialmente identiche all'implementazione ingenua di python (sebbene più veloce dell'implementazione di python che reimposta / aggiunge l'elenco, come mostrato nella modifica sopra):
$ /usr/bin/time cat test_lines_double | ./split6
15.09 real 0.01 user 0.45 sys
C++ : Saw 20000000 lines in 15 seconds. Crunch speed: 1333333
La mia unica piccola lamentela rimasta riguarda la quantità di codice necessaria per far funzionare C ++ in questo caso.
Una delle lezioni qui da questo problema e dal problema di lettura della riga standard di ieri (collegato sopra) è che si dovrebbe sempre fare il benchmark invece di fare supposizioni ingenue sulle prestazioni "predefinite" relative delle lingue. Apprezzo l'educazione.
Grazie ancora a tutti per i vostri suggerimenti!
g++ -Wall -O3 -o split1 split_1.cpp@JJC: come se la cava il tuo benchmark quando usi effettivamente dummye splinerispettivamente, forse Python rimuove la chiamata a line.split()perché non ha effetti collaterali?