boost :: tokenizer vs boost :: split

时间:2011-10-28 14:44:11

标签: c++ boost

我试图将每个'^'字符上的c ++字符串解析为矢量标记。我一直使用boost :: split方法,但我现在正在编写性能关键代码,并想知道哪一个提供了更好的性能。

例如:

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

VS

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

哪一个可以提供更好的性能?为什么?

4 个答案:

答案 0 :(得分:39)

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

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

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

我在我的机器上进行了快速测试,类似的模式有1000万个令牌,如下所示:

  • boost :: split = 2.5s ~620MB
  • boost :: tokenizer = 0.9s 0MB

如果您只是对令牌进行一次性扫描,那么显然令牌器更好。 但是,如果您要在应用程序的生命周期中粉碎成要重用的结构,那么可能首选使用标记向量。

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

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.6s 390MB 并进行测试。并且,最重要的是,此向量的内存开销与令牌的数量成线性关系 - 不依赖于令牌的长度,而std::vector<string>存储每个令牌。

答案 1 :(得分:12)

我使用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

标记生成器比<{1}} ,一次迭代的数字甚至不包括字符串副本:

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)); } }

答案 2 :(得分:2)

这可能取决于您的提升版本以及您的功能。

我们在一些使用boost :: split 1.41.0来处理数千或数十万个较小字符串(预期少于10个令牌)的逻辑中存在性能问题。当我通过性能分析器运行代码时,我们发现在boost :: split中花费了惊人的39%的时间。

我们尝试了一些简单的修复&#34;这并没有像#34那样影响性能;我们知道每次传递时我们不会有超过10个项目,因此将向量预设为10个项目&#34;。

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

答案 3 :(得分:0)

在生成令牌时对其进行处理是关键。我有一个使用正则表达式的设置,它似乎和boost :: tokenizer一样快。如果将匹配项存储在向量中,其速度至少要慢50倍