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
请注意,我尝试了两种不同的字符串分割实现。第一种(split1)使用字符串方法搜索标记,能够合并多个标记并处理众多标记(它来自这里)。第二种(split2)使用getline将字符串读取为流,不合并分隔符,并且仅支持单个分隔符字符(这是由几个StackOverflow用户在字符串分割问题的答案中发布的)。
我以各种顺序多次运行了这个程序。我的测试机器是Macbook Pro(2011年,8GB,四核),这并不太重要。我使用一个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
我做错了什么?有没有更好的方法在C ++中进行字符串拆分,不依赖于外部库(即没有boost),支持合并分隔符序列(如python的split),线程安全(因此不能使用strtok),并且其性能至少与python相当?
编辑1 / 部分解决方案?:
我试图通过让Python重置虚拟列表并每次附加到它上面来使比较更公平,就像C ++一样。 这仍然不是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中实现它,但我不会为了重新实现整个项目而牺牲程序员的生产力,因此这只是一个字符串拆分速度的实验。)
感谢大家的帮助。
最终编辑/解决方案:
请参见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++执行所需的代码量太多了。
从这个问题和昨天的stdin行读取问题(以上链接)中得出的教训之一是,应该始终进行基准测试,而不是对语言的相对“默认”性能做出幼稚的假设。感谢您的教育。
再次感谢大家的建议!
g++ -Wall -O3 -o split1 split_1.cpp
。@JJC:当您实际使用dummy
和spline
时,您的基准测试表现如何?也许Python会删除对line.split()
的调用,因为它没有副作用? - Eric