【问题标题】:Boost spirit memory leakBoost Spirit内存泄漏
【发布时间】:2016-11-09 10:03:39
【问题描述】:

我正在编写一个小程序来处理一个大文本文件并进行一些替换。问题是它永远不会停止分配新内存,所以最终它会耗尽内存。我已经将它简化为一个简单的程序,它只计算行数(参见下面的代码),同时仍然分配越来越多的内存。我必须承认,我对提升和提升精神知之甚少。你能告诉我我做错了什么吗?谢谢一百万!

#include <string>
#include <iostream>
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>

// Token ids
enum token_ids {
    ID_EOL= 100
};

// Token definition
template <typename Lexer>
    struct var_replace_tokens : boost::spirit::lex::lexer<Lexer> {
        var_replace_tokens() {
            this->self.add ("\n", ID_EOL); // newline characters
        }
    };

// Functor
struct replacer {
    typedef bool result_type;
    template <typename Token>
    bool operator()(Token const& t, std::size_t& lines) const  {
        switch (t.id()) {
        case ID_EOL:
            lines++;
            break;  
        }
        return true;
    }
}; 

int main(int argc, char **argv) {
    size_t lines=0;

    var_replace_tokens< boost::spirit::lex::lexertl::lexer< boost::spirit::lex::lexertl::token< boost::spirit::istream_iterator> > > var_replace_functor;

    cin.unsetf(std::ios::skipws);

    boost::spirit::istream_iterator first(cin);
    boost::spirit::istream_iterator last;

    bool r = boost::spirit::lex::tokenize(first, last, var_replace_functor,  boost::bind(replacer(), _1, boost::ref(lines)));

    if (r) {
        cerr<<"Lines processed: "<<lines<<endl;
    }  else {
        string rest(first, last);
        cerr << "Processing failed at: "<<rest<<" (line "<<lines<<")"<<endl;
    }
}

【问题讨论】:

  • 文件有多大/有多少行?
  • 可能你没有内存泄漏。可能输入文本文件太大而无法放入内存。
  • 它必须是multi_pass迭代器适配器。由于没有语法精神不知道什么时候可以刷。有时间我看看这个
  • 文件大小为 7 GB。
  • 据我所知,istream_iterator 负责读取输入流,而无需将整个流存储到内存中。实际上,程序从一开始就开始输出东西(不是这个,原来的)。

标签: c++ boost memory-leaks boost-spirit


【解决方案1】:

行为是设计使然。

  • 它必须是multi_pass 迭代器适配器。由于没有语法,Spirit 不知道什么时候可以刷新。 [...]

  • 据我所知,istream_iterator 负责读取输入流,而无需将整个流存储到内存中

是的。但是您没有使用std::istream_iterator。您正在使用 Boost Spirit。这是一个解析器生成器。解析器需要随机访问以进行回溯。

Spirit 通过使用 multi_pass 适配器将输入序列调整为随机访问序列来支持输入迭代器。此迭代器适配器存储一个可变大小的缓冲区¹,用于回溯。某些操作(期望点、Kleene-* 等总是贪婪的运算符等)会告诉解析器框架何时可以安全地刷新缓冲区。

问题:

你不是在解析,只是在标记化。没有任何东西告诉迭代器刷新它的缓冲区。

缓冲区是无限的,因此内存使用量会增加。当然这不是泄漏,因为一旦多通道适配迭代器的最后一个副本超出范围,共享的回溯缓冲区就会被释放。

解决方案:

最简单的解决方案是使用随机访问源。如果可以,请使用内存映射文件。

其他解决方案包括告诉多通道适配器刷新。实现的最简单方法是使用tokenize_and_parse。即使使用像 *(any_token) 这样的 faux 语法,这也足以让解析器框架相信您不会要求它回溯。

灵感:


¹ http://www.boost.org/doc/libs/1_62_0/libs/spirit/doc/html/spirit/support/multi_pass.html 默认情况下它存储一个共享双端队列。使用dd if=/dev/zero bs=1M | valgrind --tool=massif ./sotest 运行测试一段时间后查看:

清楚地显示所有内存在

100.00% (805,385,576B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->99.99% (805,306,368B) 0x4187D5: void boost::spirit::iterator_policies::split_std_deque::unique<char>::increment<boost::spirit::multi_pass<std::istream, boost::spirit::iterator_policies::default_policy<boost::spirit::iterator_policies::ref_counted, boost::spirit::iterator_policies::no_check, boost::spirit::iterator_policies::istream, boost::spirit::iterator_policies::split_std_deque> > >(boost::spirit::multi_pass<std::istream, boost::spirit::iterator_policies::default_policy<boost::spirit::iterator_policies::ref_counted, boost::spirit::iterator_policies::no_check, boost::spirit::iterator_policies::istream, boost::spirit::iterator_policies::split_std_deque> >&) (in /home/sehe/Projects/stackoverflow/sotest)
| ->99.99% (805,306,368B) 0x404BC3: main (in /home/sehe/Projects/stackoverflow/sotest)

【讨论】:

  • 非常感谢您的回答。我已经调整了解析文件并计算从标准输入读取的行数、单词数和字符数 (boost.org/doc/libs/1_62_0/libs/spirit/example/lex/…) 的 Boost 示例,它仍然不会停止分配内存。
  • @Felipe 我刚刚检查过。事实上,与 kleene-star/plus 内的discussion here suggests 期望点相反,not 刷新了迭代器。我认为这是一个未经测试的边缘案例,在多通道适配器上使用 lex 迭代器。
  • 这是workaround using the flush_multi_pass directive。在我的测试中,在 90MiB 输入文件的情况下,内存使用量从 1.3GiB 下降到 82kB
  • 非常感谢,真的。这解决了问题!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-01
  • 2012-09-22
相关资源
最近更新 更多