Próbuję przekonwertować kod z Pythona na C ++, starając się przyspieszyć i wyostrzyć moje zardzewiałe umiejętności C ++. Wczoraj byłem zszokowany, gdy naiwna implementacja odczytu wierszy ze stdin była znacznie szybsza w Pythonie niż C ++ (zobacz to ). Dzisiaj w końcu odkryłem, jak podzielić ciąg w C ++ za pomocą scalania ograniczników (podobna semantyka do metody split () w Pythonie) i teraz doświadczam deja vu! Mój kod C ++ zajmuje dużo więcej czasu, aby wykonać swoją pracę (choć nie o rząd wielkości więcej, jak to miało miejsce w przypadku wczorajszej lekcji).
Kod Pythona:
#!/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('')
Kod 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
Zauważ, że próbowałem dwóch różnych implementacji podzielonych. One (split1) używa metod ciągów do wyszukiwania tokenów i jest w stanie łączyć wiele tokenów, a także obsługiwać wiele tokenów (pochodzi stąd ). Drugi (split2) używa getline do odczytywania ciągu jako strumienia, nie łączy ograniczników i obsługuje tylko jeden znak separatora (ten został wysłany przez kilku użytkowników StackOverflow w odpowiedziach na pytania dotyczące podziału ciągów).
Prowadziłem to wiele razy w różnych zamówieniach. Moja maszyna testowa to Macbook Pro (2011, 8GB, Quad Core), nie ma to większego znaczenia. Testuję z plikiem tekstowym o długości 20 milionów wierszy z trzema kolumnami oddzielonymi spacjami, z których każda wygląda podobnie do tego: „foo.bar 127.0.0.1 home.foo.bar”
Wyniki:
$ /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
Co ja robię źle? Czy istnieje lepszy sposób na dzielenie ciągów w C ++, który nie opiera się na bibliotekach zewnętrznych (tj. Bez wzmocnienia), obsługuje łączenie sekwencji ograniczników (jak podział w Pythonie), jest bezpieczny dla wątków (więc nie ma strtoka) i którego wydajność jest co najmniej na równi z Pythonem?
Edytuj 1 / Częściowe rozwiązanie ?:
Próbowałem uczynić to bardziej sprawiedliwym porównaniem, zmuszając Pythona do resetowania listy fikcyjnej i dodawania do niej za każdym razem, tak jak robi to C ++. To wciąż nie jest dokładnie to, co robi kod C ++, ale jest trochę bliżej. Zasadniczo pętla jest teraz:
for line in sys.stdin:
dummy = []
dummy += line.split()
count += 1
Wydajność Pythona jest teraz mniej więcej taka sama jak w implementacji split1 C ++.
/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
Wciąż jestem zaskoczony, że nawet jeśli Python jest tak zoptymalizowany pod kątem przetwarzania ciągów (jak zasugerował Matt Joiner), te implementacje C ++ nie byłyby szybsze. Jeśli ktoś ma pomysł, jak zrobić to w bardziej optymalny sposób przy użyciu C ++, udostępnij swój kod. (Myślę, że moim następnym krokiem będzie próba zaimplementowania tego w czystym C, chociaż nie zamierzam rezygnować z produktywności programisty, aby ponownie zaimplementować mój ogólny projekt w C, więc będzie to tylko eksperyment dotyczący szybkości dzielenia ciągów.)
Dziękuję wszystkim za pomoc.
Ostateczna edycja / rozwiązanie:
Zobacz zaakceptowaną odpowiedź Alfa. Ponieważ Python traktuje łańcuchy wyłącznie przez odniesienie, a ciągi STL są często kopiowane, wydajność jest lepsza w przypadku implementacji Vanilla Python. Dla porównania skompilowałem i przepuściłem moje dane przez kod Alf, a tutaj jest wydajność na tej samej maszynie, co wszystkie inne uruchomione, zasadniczo identyczne z naiwną implementacją Pythona (choć szybszą niż implementacja Pythona, która resetuje / dołącza listę, ponieważ pokazane w powyższej edycji):
$ /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
Moje jedyne pozostałe zastrzeżenie dotyczy ilości kodu niezbędnego do wykonania w tym przypadku C ++.
Jedną z lekcji wyciągniętych z tego wydania i wczorajszego problemu z czytaniem wierszy standardowego (link powyżej) jest to, że należy zawsze dokonywać testów porównawczych zamiast naiwnych założeń dotyczących względnej „domyślnej” wydajności języków. Doceniam wykształcenie.
Jeszcze raz dziękuję wszystkim za sugestie!