2011-10-28 93 views
27

我想解析每个'^'字符的C++字符串到一个向量标记。我一直使用boost :: split方法,但我现在正在编写性能关键代码,并想知道哪一个可以提供更好的性能。boost :: tokenizer vs boost :: split

例如:

string message = "A^B^C^D"; 
vector<string> tokens; 
boost::split(tokens, message, boost::is_any_of("^")); 

boost::char_separator<char> sep("^"); 
boost::tokenizer<boost::char_separator<char> > tokens(text, sep); 

哪一个会提供更好的性能,为什么?

谢谢。

+32

简介它,你告诉我们。 –

+2

第二个看起来好像没有做任何内存分配,所以我猜想这会更快。但只有一种方法可以确定。 –

+5

[Boost.Spirit](http://www.boost.org/libs/spirit/)。[齐](http://www.boost.org/libs/spirit/doc/html/spirit/qi/tutorials /quick_start.html)将大幅超越两者。 – ildjarn

回答

38

最好的选择取决于几个因素。如果您只需扫描一次令牌,那么boost :: tokenizer在运行时和空间性能方面都是不错的选择(这些令牌向量可能会占用大量空间,具体取决于输入数据)。

如果你要经常扫描令牌,或者需要一个高效的随机访问向量,那么boost :: split可能是更好的选择。

例如,在你的 “A^B^C^...^Z” 输入字符串,其中令牌是长度为1个字节,该方法boost::split/vector<string>会消耗至少 2 * N-1个字节。使用字符串存储在大多数STL实现中的方式,您可以计算出其计数超过8倍。将这些字符串存储在向量中的内存和时间是昂贵的。

我跑我的机器,并拥有1000万个令牌类似的模式快速测试是这样的:

  • 的boost ::分裂= 2.5秒〜620MB
  • 的boost ::标记生成器= 0.9S0MB

如果你只是在做一时间s可以的令牌,那么显然这个令牌化器是更好的。 但是,如果您在应用程序的整个生命周期中粉碎成要重复使用的结构,则可能首选拥有一个令牌向量。

如果你想去的矢量路线,那么我建议不要使用vector<string>,而是一个字符串::迭代器的向量。只需将其分成一对迭代器,并保留大量的令牌以供参考。例如:

using namespace std; 
vector<pair<string::const_iterator,string::const_iterator> > tokens; 
boost::split(tokens, s, boost::is_any_of("^")); 
for(auto beg=tokens.begin(); beg!=tokens.end();++beg){ 
    cout << string(beg->first,beg->second) << endl; 
} 

这个改进版采用相同的服务器和测试上1.6秒390MB。并且,该向量的所有内存开销与令牌的数量成线性关系 - 不以任何方式依赖于令牌的长度,而是存储每个令牌。

10

我发现使用clang++ -O3 -std=c++11 -stdlib=libc++的结果相当不同。

首先我提取用逗号没有换行符分离成巨大的字符串〜470K字的文本文件中,像这样:

path const inputPath("input.txt"); 

filebuf buf; 
buf.open(inputPath.string(),ios::in); 
if (!buf.is_open()) 
    return cerr << "can't open" << endl, 1; 

string str(filesystem::file_size(inputPath),'\0'); 
buf.sgetn(&str[0], str.size()); 
buf.close(); 

然后我跑的各种定时测试结果存储到清除预尺寸矢量运行之间,例如,

void vectorStorage(string const& str) 
{ 
    static size_t const expectedSize = 471785; 

    vector<string> contents; 
    contents.reserve(expectedSize+1); 

    ... 

    { 
     timed _("split is_any_of"); 
     split(contents, str, is_any_of(",")); 
    } 
    if (expectedSize != contents.size()) throw runtime_error("bad size"); 
    contents.clear(); 

    ... 
} 

作为参考,定时器只是这样的:

struct timed 
{ 
    ~timed() 
    { 
     auto duration = chrono::duration_cast<chrono::duration<double, ratio<1,1000>>>(chrono::high_resolution_clock::now() - start_); 

     cout << setw(40) << right << name_ << ": " << duration.count() << " ms" << endl; 
    } 

    timed(std::string name="") : 
     name_(name) 
    {} 


    chrono::high_resolution_clock::time_point const start_ = chrono::high_resolution_clock::now(); 
    string const name_; 
}; 

我也计时一次迭代(无向量)。下面是结果:

Vector: 
           hand-coded: 54.8777 ms 
         split is_any_of: 67.7232 ms 
        split is_from_range: 49.0215 ms 
           tokenizer: 119.37 ms 
One iteration: 
           tokenizer: 97.2867 ms 
          split iterator: 26.5444 ms 
      split iterator back_inserter: 57.7194 ms 
       split iterator char copy: 34.8381 ms 

记号赋予这么多split,一个迭代数字甚至还不包括字符串拷贝:

{ 
    string word; 
    word.reserve(128); 

    timed _("tokenizer"); 
    boost::char_separator<char> sep(","); 
    boost::tokenizer<boost::char_separator<char> > tokens(str, sep); 

    for (auto range : tokens) 
    {} 
} 

{ 
    string word; 

    timed _("split iterator"); 
    for (auto it = make_split_iterator(str, token_finder(is_from_range(',', ','))); 
     it != decltype(it)(); ++it) 
    { 
     word = move(copy_range<string>(*it)); 
    } 
} 

毫不含糊的结论:使用split

2

这可能取决于你的提升版本,以及如何你的功能。

我们有一些逻辑性能问题这是使用boost ::分裂1.41.0处理几千或几十万小串(预计小于10代币)。当我通过性能分析器运行代码时,我们发现在boost :: split中花费了惊人的39%的时间。

我们尝试了一些简单的“修复”这并不会影响性能实质性像“我们知道我们不会有在每次通过10余项,所以预设的矢量10个项目。”

由于我们实际上并不需要向量,只能迭代令牌并完成相同的工作,我们将代码更改为boost :: tokenize,代码的相同部分降至运行时的1%。