Tôi đang cố gắng chuyển đổi một số mã từ Python sang C ++ với nỗ lực để đạt được một chút tốc độ và rèn giũa các kỹ năng C ++ vốn có của mình. Hôm qua, tôi đã bị sốc khi việc triển khai các dòng đọc từ stdin trong Python nhanh hơn nhiều so với C ++ (xem phần này ). Hôm nay, cuối cùng tôi đã tìm ra cách tách một chuỗi trong C ++ bằng hợp nhất các dấu phân cách (ngữ nghĩa tương tự như split ()) của python và bây giờ tôi đang trải nghiệm deja vu! Mã C ++ của tôi mất nhiều thời gian hơn để thực hiện công việc (mặc dù không phải là một thứ tự lớn hơn, như trường hợp của bài học ngày hôm qua).
Mã 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('')
Mã 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
Lưu ý rằng tôi đã thử hai cách triển khai phân tách khác nhau. Một (split1) sử dụng các phương thức chuỗi để tìm kiếm mã thông báo và có thể hợp nhất nhiều mã thông báo cũng như xử lý nhiều mã thông báo (nó xuất phát từ đây ). Thứ hai (split2) sử dụng getline để đọc chuỗi dưới dạng một luồng, không hợp nhất các dấu phân cách và chỉ hỗ trợ một ký tự mê sảng duy nhất (ký tự đó đã được một số người dùng StackOverflow đăng trong câu trả lời cho các câu hỏi về tách chuỗi).
Tôi đã chạy điều này nhiều lần trong các đơn đặt hàng khác nhau. Máy thử nghiệm của tôi là Macbook Pro (2011, 8GB, Quad Core), không có vấn đề gì nhiều. Tôi đang thử nghiệm với tệp văn bản 20M dòng có ba cột được phân tách bằng dấu cách mà mỗi cột trông tương tự như sau: "foo.bar 127.0.0.1 home.foo.bar"
Các kết quả:
$ /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
Tôi đang làm gì sai? Có cách nào tốt hơn để thực hiện phân tách chuỗi trong C ++ mà không dựa vào các thư viện bên ngoài (tức là không tăng cường), hỗ trợ hợp nhất chuỗi các dấu phân cách (như phân tách của python), luồng an toàn (vì vậy không có strtok) và có hiệu suất ít nhất ngang hàng với trăn?
Chỉnh sửa 1 / Giải pháp một phần ?:
Tôi đã thử làm cho nó một so sánh công bằng hơn bằng cách yêu cầu python đặt lại danh sách giả và thêm vào đó mỗi lần, như C ++. Đây vẫn không phải là chính xác những gì mã C ++ đang làm, nhưng nó gần hơn một chút. Về cơ bản, vòng lặp bây giờ là:
for line in sys.stdin:
dummy = []
dummy += line.split()
count += 1
Hiệu suất của python bây giờ giống như việc thực hiện C ++ 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
Tôi vẫn ngạc nhiên rằng, ngay cả khi Python được tối ưu hóa để xử lý chuỗi (như Matt Joiner đã đề xuất), thì các triển khai C ++ này sẽ không nhanh hơn. Nếu ai có ý tưởng về cách thực hiện việc này theo cách tối ưu hơn bằng C ++, vui lòng chia sẻ mã của bạn. (Tôi nghĩ rằng bước tiếp theo của tôi sẽ cố gắng thực hiện điều này bằng C thuần túy, mặc dù tôi sẽ không đánh đổi năng suất của lập trình viên để triển khai lại dự án tổng thể của mình trong C, vì vậy đây sẽ chỉ là một thử nghiệm cho tốc độ tách chuỗi.)
Cảm ơn tất cả sự giúp đỡ của bạn.
Chỉnh sửa / Giải pháp cuối cùng:
Hãy xem câu trả lời được chấp nhận của Alf. Vì python xử lý các chuỗi một cách nghiêm ngặt bằng tham chiếu và các chuỗi STL thường được sao chép, nên hiệu suất sẽ tốt hơn với các triển khai vani python. Để so sánh, tôi đã biên dịch và chạy dữ liệu của mình thông qua mã của Alf và đây là hiệu suất trên cùng một máy như tất cả các lần chạy khác, về cơ bản giống với triển khai python ngây thơ (mặc dù nhanh hơn triển khai python đặt lại / nối danh sách, như hiển thị trong chỉnh sửa ở trên):
$ /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
Điều duy nhất còn lại của tôi là về số lượng mã cần thiết để C ++ thực hiện trong trường hợp này.
Một trong những bài học ở đây từ vấn đề này và vấn đề đọc dòng stdin ngày hôm qua (được liên kết ở trên) là người ta nên luôn đánh giá chuẩn thay vì đưa ra các giả định ngây thơ về hiệu suất "mặc định" tương đối của các ngôn ngữ. Tôi đánh giá cao sự giáo dục.
Cảm ơn một lần nữa cho tất cả các đề xuất của bạn!
g++ -Wall -O3 -o split1 split_1.cpp
@JJC: Điểm chuẩn của bạn như thế nào khi bạn thực sự sử dụng dummy
và spline
tương ứng, có thể Python loại bỏ lệnh gọi đến line.split()
vì nó không có tác dụng phụ?