嗨,我想了解为什么下面的代码使用正则表达式进行拆分字符串拆分
#include<regex>
#include<vector>
#include<string>
std::vector<std::string> split(const std::string &s){
static const std::regex rsplit(" +");
auto rit = std::sregex_token_iterator(s.begin(), s.end(), rsplit, -1);
auto rend = std::sregex_token_iterator();
auto res = std::vector<std::string>(rit, rend);
return res;
}
int main(){
for(auto i=0; i< 10000; ++i)
split("a b c", " ");
return 0;
}
比下面的python代码慢
import re
for i in range(10000):
re.split(' +', 'a b c')
这是
> python test.py 0.05s user 0.01s system 94% cpu 0.070 total
./test 0.26s user 0.00s system 99% cpu 0.296 total
我在osx上使用clang ++。
使用-O3进行编译可以降低到 0.09s user 0.00s system 99% cpu 0.109 total
我将循环数增加到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
的const char *
作为回报,其中每个字符指针会指向原来的内子s
C字符串
本身。问题是,您不能这样做,因为它们中的每一个都不会被null终止(为此,请参阅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(从主干)构建了示例。也许其他正则表达式库可以执行得更好,但是在任何情况下,分配/解除分配通常都会影响性能。
这是boost::regex
用于定时 C字符串 参数样品:
real 0m1.284s
user 0m1.278s
sys 0m0.005s
此示例中相同的代码boost::regex
和std::regex
接口是相同的,只是需要更改名称空间和包含。
C ++ stdlib regex实现正处于起步阶段,它祝愿它随着时间的推移变得更好。
为了完善起见,我尝试了这一点(上面提到的“最终改进”建议),但是它并没有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;
}
对于带有vector return的情况,返回vectorstring_ref
而不是string
副本也将更便宜split
。
这个新的解决方案能够通过返回获得输出。我使用了在https://github.com/mclow/string_view中找到的MarshallClow的string_view
(已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
循环内部的内容(也可能不预先匹配任何内容),但是无论如何,您都会得到一个范围,您可以for
使用基于范围的范围进行范围调整,甚至可以使用它来填充vector
。
作为测距在iterator_range
创建string_view
了一个多原稿S string
(或一个 零终止的字符串
),这变得非常轻便,从不产生不必要串分配。
只是为了比较使用此split
实现,但实际上填充了一个,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
可以像这样工作。
问题内容: Python比Java / C#慢吗? 性能比较c-java-python-ruby-jython-jruby- groovy 这是一个优化CPython的项目:空载吞咽 问题答案: 不要混淆语言和运行时。 Python(该语言)具有许多运行时实现。 CPython通常是解释型的,并且会比本机代码C#慢。取决于Java JIT编译器,它可能比Java慢。 JYthon在JVM中进行解释
问题内容: 我一直认为Python的优势在于代码的可读性和开发速度,但是时间和内存的使用却不如C ++。 这些统计数据让我非常震惊。 您的经验告诉您关于Python与C ++的时间和内存使用情况? 问题答案: 我认为您错误地读取了这些统计信息。他们表明,Python比C ++ 慢 大约400倍,除了一个案例,Python更像是一种内存消耗。不过,就源代码大小而言,Python胜出。 我的Pytho
本文向大家介绍golang、python、php、c++、c、java、Nodejs性能对比,包括了golang、python、php、c++、c、java、Nodejs性能对比的使用技巧和注意事项,需要的朋友参考一下 本人在PHP/C++/Go/Py时,突发奇想,想把最近主流的编程语言性能作个简单的比较, 至于怎么比,还是不得不用神奇的斐波那契算法。可能是比较常用或好玩吧。 好了,tal
问题内容: 我做了一个非常简单的基准测试程序,该程序可以使用4种不同的语言计算出高达10,000,000的所有素数。 (2.97秒)-node.js(javascript)(4.4.5) (6.96秒)-c(c99) (6.91秒)-Java(1.7) (45.5秒)-python(2.7) 以上平均每次运行3次,用户时间 Node.js到目前为止运行最快。这使我感到困惑,原因有两个: 在这种情况
问题内容: 我正在尝试将一些代码从Python转换为C ,以期提高速度并提高生锈的C 技能。当一个天真的实现从标准输入读取线是在Python比C快得多 (见昨天我惊呆了这个)。今天,我终于弄清楚了如何使用合并定界符(与python的split()相似的语义)在C 中拆分字符串,并且现在遇到了deja vu!我的C ++代码需要花费更长的时间才能完成工作(尽管昨天的课程并没有增加一个数量级)。 Py
问题内容: 我想比较使用Python和从读取的字符串输入的行数,并且震惊地看到我的C ++代码比等效的Python代码慢一个数量级。由于我的生锈,并且我还不是专家,所以请告诉我我做错了什么还是误解了什么。 (TLDR答案:包括以下声明:或仅使用fgets代替。 TLDR结果:一直滚动到我的问题的底部,然后查看表格。) C ++代码: 等同于Python: 这是我的结果: 我应该注意,我在Mac O