为什么 std::bad_alloc 被抛出?

Why std::bad_alloc is thrown?

我正在实施一个 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;

注意: 通常 keyemit 中的 value 是基于模板的,但为了清楚起见,我在这个例子中省略了它们。

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

从我读到的关于 strtok 实现的内容来看,原来的 char* 被修改了(在每个标记的末尾添加 [=29=] )所以没有分配额外的内存。

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

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

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

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

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