Я пытаюсь преобразовать некоторый код из Python в C ++, чтобы немного повысить скорость и отточить свои ржавые навыки C ++.Вчера я был шокирован, когда наивная реализация чтения строк из stdin была намного быстрее в Python, чем в C ++ (см. this ).Сегодня я наконец-то понял, как разбить строку в C ++ с помощью слитных разделителей (семантика похожа на python split ()), и теперь я испытываю дежа вю!Мой код на C ++ занимает намного больше времени (хотя и не на порядок больше, как в случае с вчерашним уроком).
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 ++ Код:
#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, 8 ГБ, Quad Core), но это не так уж важно.Я тестирую текстовый файл размером 20 Мб с тремя разделенными пробелами столбцами, каждый из которых выглядит примерно так: "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 ++, который не зависит от внешних библиотек (то есть без повышения), поддерживает объединение последовательностей разделителей (например, разделение Python), является потокобезопасным (так что нет 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 настолько оптимизирован для обработки строк (как предложил Мэтт Джоинер), эти реализации C ++ не будут быстрее.Если у кого-то есть идеи о том, как сделать это более оптимальным способом с помощью C ++, поделитесь своим кодом.(Я думаю, что моим следующим шагом будет попытка реализовать это на чистом C, хотя я не собираюсь жертвовать производительностью программиста, чтобы заново реализовать мой общий проект на C, так что это будет просто эксперимент по скорости разделения строк.)
Спасибо всем за помощь.
Окончательное редактирование / решение:
Пожалуйста, ознакомьтесь с принятым ответом Альфа.Поскольку python имеет дело со строками строго по ссылке, а строки STL часто копируются, производительность лучше в реализациях vanilla python.Для сравнения, я скомпилировал и прогнал свои данные с помощью кода Альфа, и здесь приведена производительность на том же компьютере, что и на всех других запусках, по сути, идентичная реализации наивного 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 ++ в этом случае.
Один из уроков этого выпуска и вчерашней проблемы чтения строк стандартного ввода (ссылка выше) заключается в том, что следует всегда ставить тесты вместо того, чтобы делать наивные предположения об относительной производительности языков по умолчанию.Я ценю образование.
Еще раз спасибо всем за ваши предложения!