少し速度を上げて、さびたC ++のスキルを磨くために、一部のコードをPythonからC ++に変換しようとしています。昨日、標準入力から行を読み取る単純な実装がPythonではC ++よりもはるかに高速だったときにショックを受けました(これを参照)。今日、ようやくC ++で文字列をマージ区切り文字(pythonのsplit()と同様のセマンティクス)で分割する方法を見つけ、deja vuを体験しました!私のC ++コードは、作業を実行するのにはるかに長い時間がかかります(昨日のレッスンの場合のように、1桁以上ではありません)。
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('')
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
2つの異なる分割実装を試したことに注意してください。一つ(split1)は、トークンを検索するための文字列メソッドを使用し、複数のトークンだけでなく、ハンドルの多数のトークンを(それはから来てマージすることができ、ここで)。2番目(split2)は、getlineを使用して文字列をストリームとして読み取り、区切り文字をマージせず、1つの区切り文字(文字列分割の質問に対する回答で複数のStackOverflowユーザーによって投稿されたもの)のみをサポートします。
これをさまざまな順序で複数回実行しました。私のテストマシンはMacbook Pro(2011、8GB、クアッドコア)ですが、それほど重要ではありません。スペースで区切られた3つの列があり、それぞれが次のように見える20Mの行テキストファイルでテストしています: "foo.bar 127.0.0.1 home.foo.bar"
結果:
$ /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
何が悪いのですか?外部ライブラリに依存せず(つまり、ブーストなし)、区切り文字のマージシーケンス(pythonの分割など)をサポートし、スレッドセーフ(strtokなし)で、少なくともパフォーマンスがC ++で文字列分割を行うより良い方法はありますかPythonと同等ですか?
Edit 1 / Partial Solution ?:
C ++のように、Pythonにダミーリストをリセットして毎回追加させることで、より公平な比較を試みました。これはまだC ++コードが行っていることとは正確には同じではありませんが、少し近づいています。基本的に、ループは今です:
for line in sys.stdin:
dummy = []
dummy += line.split()
count += 1
Pythonのパフォーマンスは、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
Pythonが文字列処理用に非常に最適化されていても(Matt Joinerが示唆したように)、これらのC ++実装が高速にならないことにはまだ驚きます。C ++を使用してより最適な方法でこれを行う方法について誰かがアイデアを持っている場合は、コードを共有してください。(私の次のステップはこれを純粋なCで実装しようとすることですが、プログラマーの生産性を犠牲にして私のプロジェクト全体をCで再実装することはないので、これは文字列分割速度の実験にすぎません。)
皆さんの助けに感謝します。
最終編集/ソリューション:
Alfの承認済み回答を参照してください。Pythonは文字列を厳密に参照で扱い、STL文字列はコピーされることが多いため、バニラPython実装を使用するとパフォーマンスが向上します。比較のために、私はAlfのコードを使用してデータをコンパイルおよび実行しました。これは、他のすべての実行と同じマシンでのパフォーマンスです。基本的に、単純なpython実装と同じです(ただし、リストをリセット/追加するpython実装よりも高速です)。上記の編集に示されています):
$ /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
私の唯一残っている不満は、この場合にC ++を実行するために必要なコードの量に関するものです。
この問題と昨日の標準入力の読み取りの問題(上記にリンク)からの教訓の1つは、言語の相対的な「デフォルト」のパフォーマンスについて単純な仮定をするのではなく、常にベンチマークを行う必要があるということです。教育に感謝します。
あなたの提案をありがとうございました!
g++ -Wall -O3 -o split1 split_1.cpp
@JJC:あなたが実際に使用するときにどのようにあなたのベンチマークの運賃が行うdummy
とspline
、それぞれ、多分Pythonはへの呼び出しを削除しline.split()
、それが何の副作用を持っていないので?