Ich versuche, Code von Python nach C ++ zu konvertieren, um ein wenig Geschwindigkeit zu gewinnen und meine verrosteten C ++ - Fähigkeiten zu verbessern. Gestern war ich schockiert, als eine naive Implementierung des Lesens von Zeilen aus stdin in Python viel schneller war als in C ++ (siehe hier ). Heute habe ich endlich herausgefunden, wie man einen String in C ++ mit zusammengeführten Trennzeichen teilt (ähnliche Semantik wie pythons split ()), und jetzt erlebe ich deja vu! Mein C ++ - Code benötigt viel länger, um die Arbeit zu erledigen (allerdings nicht um eine Größenordnung mehr, wie es in der gestrigen Lektion der Fall war).
Python-Code:
#!/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('')
C ++ - Code:
#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
Beachten Sie, dass ich zwei verschiedene Split-Implementierungen ausprobiert habe. One (split1) verwendet String-Methoden, um nach Token zu suchen, und kann mehrere Token zusammenführen sowie zahlreiche Token verarbeiten (es kommt von hier ). Der zweite (split2) verwendet getline, um die Zeichenfolge als Stream zu lesen, führt keine Trennzeichen zusammen und unterstützt nur ein einzelnes Begrenzungszeichen (das von mehreren StackOverflow-Benutzern in Antworten auf Fragen zur Zeichenfolgenaufteilung veröffentlicht wurde).
Ich habe dies mehrmals in verschiedenen Reihenfolgen ausgeführt. Mein Testgerät ist ein Macbook Pro (2011, 8 GB, Quad Core), nicht dass es viel ausmacht. Ich teste mit einer 20-Millionen-Zeilentextdatei mit drei durch Leerzeichen getrennten Spalten, die jeweils ähnlich aussehen: "foo.bar 127.0.0.1 home.foo.bar"
Ergebnisse:
$ /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
Was mache ich falsch? Gibt es eine bessere Möglichkeit zum Aufteilen von Zeichenfolgen in C ++, die nicht auf externen Bibliotheken basiert (dh keinen Boost), das Zusammenführen von Trennzeichenfolgen unterstützt (wie die Aufteilung von Python), threadsicher ist (also kein strtok) und deren Leistung mindestens ist? auf Augenhöhe mit Python?
Bearbeiten 1 / Teillösung?:
Ich habe versucht, den Vergleich fairer zu gestalten, indem Python die Dummy-Liste zurückgesetzt und jedes Mal angehängt hat, wie dies in C ++ der Fall ist. Dies ist immer noch nicht genau das, was der C ++ - Code tut, aber es ist ein bisschen näher. Grundsätzlich ist die Schleife jetzt:
for line in sys.stdin:
dummy = []
dummy += line.split()
count += 1
Die Leistung von Python entspricht jetzt in etwa der von 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
Ich bin immer noch überrascht, dass diese C ++ - Implementierungen nicht schneller wären, selbst wenn Python für die Zeichenfolgenverarbeitung so optimiert ist (wie Matt Joiner vorgeschlagen hat). Wenn jemand Ideen hat, wie dies mit C ++ optimaler gemacht werden kann, teilen Sie bitte Ihren Code mit. (Ich denke, mein nächster Schritt wird darin bestehen, dies in reinem C zu implementieren, obwohl ich die Produktivität der Programmierer nicht abwägen werde, um mein Gesamtprojekt in C erneut zu implementieren. Dies wird also nur ein Experiment für die Geschwindigkeit der Zeichenfolgenaufteilung sein.)
Vielen Dank an alle für Ihre Hilfe.
Endgültige Bearbeitung / Lösung:
Bitte lesen Sie die akzeptierte Antwort von Alf. Da Python Strings ausschließlich als Referenz behandelt und STL-Strings häufig kopiert werden, ist die Leistung bei Vanilla-Python-Implementierungen besser. Zum Vergleich habe ich meine Daten über Alfs Code kompiliert und ausgeführt. Hier ist die Leistung auf demselben Computer wie bei allen anderen Läufen, die im Wesentlichen mit der naiven Python-Implementierung identisch ist (obwohl sie schneller ist als die Python-Implementierung, mit der die Liste zurückgesetzt / angehängt wird) in der obigen Bearbeitung gezeigt):
$ /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
Mein einziger kleiner Kritikpunkt betrifft die Menge an Code, die erforderlich ist, damit C ++ in diesem Fall funktioniert.
Eine der Lehren aus dieser Ausgabe und der gestrigen Ausgabe zum Lesen von Standardzeilen (siehe oben) ist, dass man immer einen Benchmark durchführen sollte, anstatt naive Annahmen über die relative "Standard" -Leistung von Sprachen zu treffen. Ich schätze die Ausbildung.
Nochmals vielen Dank an alle für Ihre Vorschläge!
g++ -Wall -O3 -o split1 split_1.cpp
@JJC: Wie sieht Ihr Benchmark - Tarif , wenn Sie tatsächlich nutzen dummy
und spline
jeweils vielleicht Python entfernt den Aufruf , line.split()
weil es keine Nebenwirkungen hat?