Зауважте
Дивіться також цю відповідь: https://stackoverflow.com/a/21708215, яка була основою для EDIT 2 внизу тут.
Я збільшив цикл до 1000000, щоб отримати кращий показник часу.
Це мій час Python:
real 0m2.038s
user 0m2.009s
sys 0m0.024s
Ось еквівалент вашого коду, трохи гарніший:
#include <regex>
#include <vector>
#include <string>
std::vector<std::string> split(const std::string &s, const std::regex &r)
{
return {
std::sregex_token_iterator(s.begin(), s.end(), r, -1),
std::sregex_token_iterator()
};
}
int main()
{
const std::regex r(" +");
for(auto i=0; i < 1000000; ++i)
split("a b c", r);
return 0;
}
Час:
real 0m5.786s
user 0m5.779s
sys 0m0.005s
Це оптимізація, щоб уникнути побудови / розподілу векторних та рядкових об’єктів:
#include <regex>
#include <vector>
#include <string>
void split(const std::string &s, const std::regex &r, std::vector<std::string> &v)
{
auto rit = std::sregex_token_iterator(s.begin(), s.end(), r, -1);
auto rend = std::sregex_token_iterator();
v.clear();
while(rit != rend)
{
v.push_back(*rit);
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<std::string> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Час:
real 0m3.034s
user 0m3.029s
sys 0m0.004s
Це майже на 100% покращення продуктивності.
Вектор створюється перед циклом і може збільшити свою пам’ять за першу ітерацію. Після цього не відбувається звільнення пам'яті clear()
, вектор підтримує пам'ять і будує рядки на місці .
Ще одним підвищенням продуктивності було б уникнення будівництва / руйнування std::string
повністю, а отже, розподілу / вивільнення об'єктів.
Це орієнтовно в цьому напрямку:
#include <regex>
#include <vector>
#include <string>
void split(const char *s, const std::regex &r, std::vector<std::string> &v)
{
auto rit = std::cregex_token_iterator(s, s + std::strlen(s), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.push_back(*rit);
++rit;
}
}
Час:
real 0m2.509s
user 0m2.503s
sys 0m0.004s
Кінцевим покращенням було б повернення std::vector
of const char *
as, де кожен вказівник char вказував би на підрядок всередині самого оригінального s
рядка c . Проблема в тому, що ви не можете цього зробити, тому що кожен з них не буде припинений до нуля (для цього див. Використання C ++ 1ystring_ref
.
Останнього покращення також можна досягти за допомогою цього:
#include <regex>
#include <vector>
#include <string>
void split(const std::string &s, const std::regex &r, std::vector<std::string> &v)
{
auto rit = std::cregex_token_iterator(s.data(), s.data() + s.length(), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.push_back(*rit);
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<std::string> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Я побудував зразки з дзвінком 3.3 (із стовбура) за допомогою -O3. Можливо, інші бібліотеки регулярних виразів можуть працювати ефективніше, але в будь-якому випадку розподіли / вивільнення часто є результатом.
Boost.Regex
Це boost::regex
час для зразка аргументів рядка c :
real 0m1.284s
user 0m1.278s
sys 0m0.005s
Той самий код boost::regex
та std::regex
інтерфейс у цьому зразку ідентичні, просто необхідні для зміни простору імен та включення.
Найкращі побажання, щоб з часом воно покращувалось, реалізації регулярних виразів C ++ stdlib перебувають у зародковому стані.
РЕДАГУВАТИ
Для завершення я спробував це (згадана вище пропозиція "остаточного вдосконалення"), і це нічим не покращило продуктивність еквівалентної std::vector<std::string> &v
версії:
#include <regex>
#include <vector>
#include <string>
template<typename Iterator> class intrusive_substring
{
private:
Iterator begin_, end_;
public:
intrusive_substring(Iterator begin, Iterator end) : begin_(begin), end_(end) {}
Iterator begin() {return begin_;}
Iterator end() {return end_;}
};
using intrusive_char_substring = intrusive_substring<const char *>;
void split(const std::string &s, const std::regex &r, std::vector<intrusive_char_substring> &v)
{
auto rit = std::cregex_token_iterator(s.data(), s.data() + s.length(), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.emplace_back(rit->first, rit->second);
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<intrusive_char_substring> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Це пов’язано з пропозицією array_ref та string_ref . Ось зразок коду з його використанням:
#include <regex>
#include <vector>
#include <string>
#include <string_ref>
void split(const std::string &s, const std::regex &r, std::vector<std::string_ref> &v)
{
auto rit = std::cregex_token_iterator(s.data(), s.data() + s.length(), r, -1);
auto rend = std::cregex_token_iterator();
v.clear();
while(rit != rend)
{
v.emplace_back(rit->first, rit->length());
++rit;
}
}
int main()
{
const std::regex r(" +");
std::vector<std::string_ref> v;
for(auto i=0; i < 1000000; ++i)
split("a b c", r, v);
return 0;
}
Також буде дешевше повернути вектор, string_ref
а не string
копіювати у випадку split
з векторним поверненням.
РЕДАГУВАТИ 2
Це нове рішення здатне отримувати результат шляхом повернення. Я використовував string_view
( string_ref
перейменований) реалізацію libc ++ Маршалла Клоу, знайдену за адресою https://github.com/mclow/string_view .
#include <string>
#include <string_view>
#include <boost/regex.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/iterator/transform_iterator.hpp>
using namespace std;
using namespace std::experimental;
using namespace boost;
string_view stringfier(const cregex_token_iterator::value_type &match) {
return {match.first, static_cast<size_t>(match.length())};
}
using string_view_iterator =
transform_iterator<decltype(&stringfier), cregex_token_iterator>;
iterator_range<string_view_iterator> split(string_view s, const regex &r) {
return {
string_view_iterator(
cregex_token_iterator(s.begin(), s.end(), r, -1),
stringfier
),
string_view_iterator()
};
}
int main() {
const regex r(" +");
for (size_t i = 0; i < 1000000; ++i) {
split("a b c", r);
}
}
Час:
real 0m0.385s
user 0m0.385s
sys 0m0.000s
Зверніть увагу, наскільки швидше це порівняно з попередніми результатами. Звичайно, це не заповнення vector
внутрішнього циклу (а також, мабуть, заздалегідь нічого не збігається), але ви в будь-якому випадку отримуєте діапазон, який ви можете діапазонувати на основі діапазону for
, або навіть використовувати його для заповненняvector
.
Оскільки розподіл розмірів по iterator_range
створює string_view
s над оригіналом string
(або рядком, що закінчується нулем ), це стає дуже легким, ніколи не генеруючи непотрібних розподілів рядків.
Просто для порівняння використання цієї split
реалізації, але насправді заповнення a vector
ми могли б зробити це:
int main() {
const regex r(" +");
vector<string_view> v;
v.reserve(10);
for (size_t i = 0; i < 1000000; ++i) {
copy(split("a b c", r), back_inserter(v));
v.clear();
}
}
Тут використовується алгоритм копіювання діапазону посилення, щоб заповнити вектор у кожній ітерації, час:
real 0m1.002s
user 0m0.997s
sys 0m0.004s
Як бачимо, великої різниці в порівнянні з оптимізованою string_view
версією параметру виводу немає.
Також зверніть увагу, що є пропозиція щодо такоїstd::split
роботи.