【问题标题】:Why std::bad_alloc is thrown?为什么抛出 std::bad_alloc ?
【发布时间】:2016-04-04 01:24:55
【问题描述】:

我正在实施一个 map/reduce 并行项目。但是,对于一个字数玩具示例,使用(或多或少)1GB 的输入文件,只有一个映射器(映射整个文件)我收到std::bad_alloc 异常。不幸的是,这只发生在远程 Xeon Phi(RAM 较小)上,因此没有深度调试。

但是,内存在两个地方被占用:当映射器读取(存储)整个文件在char *中时:

void getNextKeyValue() {
    key = pos;//int
    value = new char[file_size];//file_size only with 1 mapper
    ssize_t result = pread(fd, value, file_size, pos);
    assert(result == ( file_size ) );
    morePairs = false;
}

另一个是调用map 函数并将一系列pair<char*,int> 存储在vector 中作为映射的结果:

地图功能:

std::function<void(int key, char *value,MapResult<int,char*,char*,int> *result)> map_func = [](int key,char *value,MapResult<int,char*,char*,int> *result) {
    const char delimit[]=" \t\r\n\v\f";
    char *token , *save;
    token = strtok_r(value, delimit, &save);
    while (token != NULL){
        result->emit(token,1);
        token = strtok_r (NULL,delimit, &save);
    }
};

emit 实现(以及地图的结果生成):

    void emit(char* key, int value) {
        res.push_back(pair<char*,int>(key,value));
    }
    ...
    private:
    vector<pair<char*,int>> res;

注意:通常emit 中的keyvalue 是基于模板的,但为了清楚起见,在本例中我省略了它们。

首先我认为std::bad_alloc 是因为char *value(占用1GB)而引发的,但是在value 分配之后放置了测试cout 消息后引发了异常(所以这不是问题)。

根据我所读到的关于strtok 实现的内容,原始的char* 被修改(在每个标记的末尾添加\0),因此没有分配额外的内存。

唯一剩下的可能性是vector&lt;pair&lt;char*,int&gt;&gt; 占用的空间,但我无法计算它的空间(请帮助我)。假设平均字长为 5 个字符,我们应该有 ~ 2*10^8 个字。

1201ProgramAlarm's answer 之后的更新: 不幸的是,预先计算字数然后调用resize() 以消除未使用的向量的内存是不可行的,原因有两个:

  1. 这会大大降低性能。如果不调用 emit 并且仅计算 280MB 文件的字数,则在 1329 毫秒的总执行时间中需要 1242 毫秒(第一次读取文件时约为 5000 秒)。
  2. 使用此解决方案,最终用户在编写 map 函数时应深入考虑内存使用情况,这在 Hadoop 等经典 map/reduce 框架中通常不会发生。

【问题讨论】:

  • 您是否尝试使用调试器查看异常是在哪里引发的?
  • 正如我在问题中所说,我无法调试,因为我在远程 Xeon Phi 上启动程序。

标签: c++ memory-management vector mapreduce bad-alloc


【解决方案1】:

问题不在于vector 使用的空间,而是向量先前在其容量较小时使用的所有空间。除非您在向量上调用 reserve,否则当您推送第一个元素时,它会开始为空并分配少量空间(通常足够大以容纳一个元素)。在以后的推送中,如果没有分配足够的剩余空间,它将分配更多(当前大小的 1.5 倍或 2 倍)。这意味着您需要足够的空闲内存来存储较小的大小和较大的大小。因为释放的内存块在组合后仍然不足以满足下一个更大的请求量,因此可能会有很多空闲但未使用的内存。

您应该调用res.reserve(/*appropriate large size*/),或将容器切换到deque,虽然它最终需要更多空间,但随着它的增长不需要重新分配。要获得要保留的大小,您可以遍历文件一次以查看其中实际包含多少字,为它们保留空间,然后再次遍历并保存单词。

【讨论】:

  • 回答更新问题
猜你喜欢
  • 2012-01-16
  • 2012-08-08
  • 1970-01-01
  • 1970-01-01
  • 2012-08-24
  • 1970-01-01
  • 2019-04-21
  • 2016-02-10
  • 1970-01-01
相关资源
最近更新 更多