Я намагаюсь перетворити якийсь код з Python на C ++, намагаючись набрати трохи швидкості і відточити свої іржаві навички C ++. Вчора я був шокований, коли наївна реалізація рядків читання з stdin була набагато швидшою в Python, ніж C ++ (див. Це ). Сьогодні я нарешті з'ясував, як розділити рядок на C ++ зі злиттям роздільників (схожа семантика на розкол пітона ()), і зараз я відчуваю дежавю! Мій код C ++ займає набагато більше часу (хоча це не на порядок більше, як це було у вчорашньому уроці).
Код 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 р., 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) і продуктивність якого принаймні нарівні з пітоном?
Редагувати 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, тому це буде просто експериментом для швидкості розбиття рядків.)
Дякуємо всім за вашу допомогу.
Остаточне редагування / рішення:
Будь ласка, дивіться прийняту відповідь Альфа. Оскільки python обробляє рядки строго за посиланням, а рядки STL часто копіюються, продуктивність кращої для реалізації ванільного 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 ++ для виконання у цьому випадку.
Одним із уроків цього випуску та вчорашнього питання читання строгого рядка (зв'язане вище) є те, що завжди слід орієнтуватися, а не робити наївні припущення щодо відносної «мови за умовчанням» у мовах. Я ціную освіту.
Ще раз дякую всім за ваші пропозиції!
g++ -Wall -O3 -o split1 split_1.cpp
@JJC: Як ваш базовий тариф, коли ви фактично використовуєте, dummy
і spline
відповідно, можливо, Python видаляє виклик, line.split()
оскільки він не має побічних ефектів?