【问题标题】:Nest vectors consumes to much memory in c++ [duplicate]嵌套向量在c ++中消耗大量内存[重复]
【发布时间】:2014-03-23 11:58:13
【问题描述】:

我试图弄清楚为什么我的应用程序消耗了太多内存。这里是:

#include <iostream>
#include <sstream>
#include <string>
#include <exception>
#include <algorithm>
#include <vector>
#include <utility>
#include <assert.h>
#include <limits.h>
#include <time.h>
#include <tchar.h>
#include <random>

typedef unsigned __int32 uint;

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    vector<vector<uint>> arr(65536 * 16, vector<uint>());
    mt19937 mt;
    mt.seed(time(NULL));
    uniform_int<uint> generator(0, arr.size() - 1);
    for (uint i = 0; i < 10000000; i++)
    {
        for (uint j = 0; j < 16; j++)
        {
            uint bucketIndex = generator(mt);
            arr[bucketIndex].push_back(i);
        }
    }

    uint cap = 0;
    for (uint i = 0; i < arr.size(); i++)
    {
        cap += sizeof(uint) * arr[i].capacity() + sizeof(arr[i]);
    }
    cap += sizeof(vector<uint>) * arr.capacity() + sizeof(vector<vector<uint>>);
    cout << "Total bytes: " << cap << endl;
    cout << "Press any key..." << endl;
    cin.get();
}

我使用 Windows 7 64 位和 Visual Studio 2010,代码也编译为 64 位。

代码在调试和发布中输出以下内容

总字节数:914591424

看起来正确(您可以手动检查),但内存管理器显示应用程序消耗了 ~ 1.4 GB 的 RAM

这 500 兆字节是从哪里来的?你能告诉我如何解决这个问题吗?

更新

问题是由内存碎片引起的。可以通过不时压缩内存来解决。

【问题讨论】:

  • 不幸的是,我没有在那里找到我的问题的答案。
  • @tinnulion 我认为您需要仔细观察。另外,看看保留集(虚拟内存很有趣)。
  • 对不起,我没明白。能不能详细解释一下?
  • 您的代码几乎无法编译。它缺少标题并使用不存在的类。它使用非便携式主机。它缺少使用命名空间来使其工作。基本上,“wat”。
  • @sehe 这会使内存碎片化。根据分配器的不同,它可能会将您的块之间的间隙显示为空闲,但孔通常只是“廉价”内存,不能用于更大的分配,并且将您以后的分配填充到彼此相距很远的间隙中,通常会导致很多缓存未命中通过搞砸地方。如果谈到高效的内存使用,那么紧凑的结构和内存池是赢家。

标签: c++ vector nested memory-consumption


【解决方案1】:

这是因为每个向量都包含三个指针(或它们的道德和大小等价物):开始、开始 + 大小和开始 + 容量。因此,当您有一个包含大量其他小向量的向量时,每个内部向量会浪费三个字(在 64 位系统上是 24 个字节)。

并且由于每个内部向量的 begin() 指向一个单独的分配,因此您需要支付 N 倍的分配开销成本。这可能是另外几个字节。

相反,您可能希望分配一个大区域并将其视为二维数组。或者使用提供此类功能的众多库之一。如果您的内部向量具有不同的大小,这将不起作用,但它们通常都是一个大小,因此您确实想要一个 2D“矩形”,而不是向量的向量。

【讨论】:

  • 我知道使用 vector> 时的内存开销,但我必须使用它。
  • 其实我的回答是关于别的。看代码。它总结了所有向量的所有容量。这个数量的内存应用程序必须消耗。但它消耗更多!
  • 我添加了关于内存分配开销的第二段。这就是其中一些肯定来自的地方。也许所有这些都取决于您运行它时的平台和环境。至于为什么你“必须”使用这个次优的解决方案,好吧,如果是这样,当它不是次优的时候不要抱怨。 :)
  • 在 64 位系统上,在没有碎片的连续内存区域上浪费了 24 MB 的内存。在 32 位系统上,它只有一半。这不是浪费数百兆字节的原因。频繁的重新分配通常比“几个字节”昂贵得多;-)
【解决方案2】:

我与 Boost Container 的向量进行了比较。并添加了shrink_to_fit。区别:

Total bytes: 690331672    // boost::container::vector::shrink_to_fit()
Total bytes: 1120033816   // std::vector

(另请注意,boost 容器永远不会在默认构造时动态分配。)

代码如下(改动不大):

#include <iostream>
#include <exception>
#include <algorithm>
#include <vector>
#include <utility>
#include <cassert>
#include <cstdint>
#include <random>

#include <boost/optional.hpp>
#include <boost/container/vector.hpp>

using boost::container::vector;
using boost::optional;

int main()
{
    vector<vector<uint32_t>> arr(1<<20);
    std::mt19937 mt;
    mt.seed(time(NULL));

    std::uniform_int_distribution<uint32_t> generator(0, arr.size() - 1);
    for (uint32_t i = 0; i < 10000000; i++)
    {
        for (uint32_t j = 0; j < 16; j++)
        {
            auto& bucket = arr[generator(mt)];
            //if (!bucket) bucket = vector<uint32_t>();

            bucket.push_back(i);
        }
    }

    for(auto& i : arr)
        i.shrink_to_fit();

    uint32_t cap = 0;
    for (uint32_t i = 0; i < arr.size(); i++)
    {
        cap += sizeof(uint32_t) * arr[i].capacity() + sizeof(arr[i]);
    }
    cap += sizeof(vector<uint32_t>) * arr.capacity() + sizeof(arr);
    std::cout << "Total bytes: " << cap << std::endl;
    std::cout << "Press any key..." << std::endl;
    std::cin.get();
}

更新内存配置文件运行

--------------------------------------------------------------------------------
Command:            ./test
Massif arguments:   (none)
ms_print arguments: massif.out.4193
--------------------------------------------------------------------------------


    MB
822.7^                                                                      # 
     |                                                                    @@# 
     |                                                                  @@@@#:
     |                                                                @@@@@@#:
     |                                                              @@@@@@@@#:
     |                                                          :@@@@@@@@@@@#:
     |                                                   :::::@@:@@@@@@@@@@@#:
     |                                                 @@:: ::@ :@@@@@@@@@@@#:
     |                                              @@@@@:: ::@ :@@@@@@@@@@@#:
     |                                            @@@ @@@:: ::@ :@@@@@@@@@@@#:
     |                                        :::@@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |                                   @@@:::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |                                @@@@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |                             @@@@ @@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |                         @@@:@@@@ @@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |                     @@@@@@ :@@@@ @@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |                   @@@@ @@@ :@@@@ @@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |               @@@@@@@@ @@@ :@@@@ @@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     |        :::::::@@ @@@@@ @@@ :@@@@ @@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
     | ::@:@:::: ::: @@ @@@@@ @@@ :@@@@ @@@ :::: @@@@ @@@:: ::@ :@@@@@@@@@@@#:
   0 +----------------------------------------------------------------------->Gi
     0                                                                   69.85

【讨论】:

  • 谢谢。是的,s true because shrink_to_fit 会减少所有容器的容量。你能说出这段代码消耗了多少内存吗(通过某种内存管理器)?
  • 用一些分析数据更新了答案。
  • 非常感谢!我会尝试提升向量,看看是否有帮助。
【解决方案3】:

问题是您不知道数组的确切大小,否则您可以在实际填充它们之前使用reserve 设置向量容量,这样可以避免碎片。请尝试以下操作:

  1. 生成随机种子 (time(NULL)) 并保存以备后用。
  2. 创建一个数组大小为65536 * 16std::vector&lt;uint&gt; 并将其中的所有整数/计数器初始化为零,让我们将此数组/向量命名为“vec_sizes”。我们将使用这个数组来存储/找出我们稍后将创建/填充的数组的大小。
  3. 使用在步骤 #1 中获取的种子初始化随机生成器。
  4. 运行您的算法(嵌套的 for 循环),但不要像代码中的 arr[bucketIndex].push_back(i); 那样将项目存储到二维向量中,只需增加 vec_sizes[bucketIndex] 计数器即可。

现在我们知道了所有向量的大小。

  1. 创建您的arr 向量。
  2. 对于arr 中的所有子向量,调用具有在vec_sizes 向量中找到的相应大小的向量的reserve 方法。这应该可以有效地预分配向量,并且可以避免重新分配。
  3. 使用我们在步骤 #1 中存储的相同种子初始化随机生成器。
  4. 运行您的算法。现在将数据推送到向量中不会重新分配,因为它们的存储空间已经由您的 reserve 调用分配。

在这里,我们利用了这样一个事实,即您使用的是伪随机生成器,如果您从同一个种子开始运行两次,它会给出完全相同的数字序列。

注意:当内存效率为目标时,解决方案通常会做两次工作:首先计算最终数据的不同维度,然后非常有效地/“紧凑地”分配空间,然后填满有效分配的存储空间。通常你必须牺牲一些东西。

【讨论】:

  • 它应该在特定情况下解决问题。但是我使用随机数生成器来显示问题并没有真正的应用。
  • @tinnulion 你知道什么是伪随机发生器吗? :-) 坦率地说...
  • 当然。甚至知道梅森的 Twister 是如何工作的。
  • @tinnulion 如果您使用的是伪随机生成器,那么我的回答可能是最好的解决方案之一。如果您使用真正的随机熵,那么您只能通过利用问题的某些属性来找到有效的解决方案,一个简单的解决方案可以通过双倍内存使用和复制来压缩您的数据结构,可选地使用池来帮助放置数据块(和碎片块)到连续区域。一个二维数组只有通过实现/使用它自己的内部分配器才能有效地压缩自己,这会使它变得相当复杂。
猜你喜欢
  • 2018-03-01
  • 1970-01-01
  • 2022-01-16
  • 2014-01-04
  • 2012-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-04
相关资源
最近更新 更多