注意
另请参阅此答案: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 *
,其中每个 char 指针将指向原始s
c 字符串本身内的一个子字符串。问题是,您不能这样做,因为它们中的每一个都不会以空值终止(为此,请参阅string_ref
后面示例中 C++1y 的用法)。
最后的改进也可以通过以下方式实现:
#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); // the constant string("a b c") should be optimized
// by the compiler. I got the same performance as
// if it was an object outside the loop
return 0;
}
我用-O3 用clang 3.3(来自主干)构建了样本。也许其他正则表达式库能够表现得更好,但无论如何,分配/解除分配经常会影响性能。
升压正则表达式
这是c 字符串参数示例的boost::regex
时间:
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(); // This can potentially be optimized away by the compiler because
// the intrusive_char_substring destructor does nothing, so
// resetting the internal size is the only thing to be done.
// Formerly allocated memory is maintained.
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
这种新的解决方案能够通过返回获得输出。我使用了https://github.com/mclow/string_viewstring_view
上的 Marshall Clow 的(已string_ref
重命名)libc++ 实现。
#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
在循环内部填充 a (也可能事先也不匹配任何东西),但无论如何你都会得到一个范围,你可以使用 range-based 来覆盖for
它,甚至可以用它来填充 a vector
。
由于在原始(或以空结尾的字符串)上的 create s 范围内,这变得非常轻量级,不会产生不必要的字符串分配iterator_range
。string_view
string
只是为了比较使用这个split
实现但实际上填充 avector
我们可以这样做:
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();
}
}
这使用boost range copy算法在每次迭代中填充向量,时间为:
real 0m1.002s
user 0m0.997s
sys 0m0.004s
可以看出,与优化后的string_view
输出参数版本相比没有太大区别。
另请注意,有一个提案std::split
可以像这样工作。