【问题标题】:c++ Alternative implementation to avoid shifting between RAM and SWAP memoryc++ 避免在 RAM 和 SWAP 内存之间转换的替代实现
【发布时间】:2014-05-30 10:15:57
【问题描述】:

我有一个程序,它使用动态规划来计算一些信息。问题是,理论上使用的内存呈指数增长。我使用的一些过滤器限制了这个空间,但是对于大量输入,它们也无法避免我的程序耗尽 RAM - 内存

程序在 4 个线程上运行。当我以非常大的输入运行它时,我注意到,在某些时候程序开始使用交换内存,因为我的 RAM 不够大。这样做的结果是,我的 CPU 使用率从大约 380% 下降到 15% 或更低

只有一个变量使用内存,它是以下数据结构:

使用CLN library编辑(添加类型):

class My_Map {

    typedef std::pair<double,short> key;
    typedef cln::cl_I value;

public: 
    tbb::concurrent_hash_map<key,value>* map;

    My_Map()  { map = new tbb::concurrent_hash_map<myType>(); }
    ~My_Map() { delete map; }

    //some functions for operations on the map
};

在我的主程序中,我使用这个数据结构作为全局变量:

My_Map* container = new My_Map();

问题:

有没有办法避免内存在 SWAP 和 RAM 之间转移?我认为将所有内存推送到Heap 会有所帮助,但似乎没有。所以我不知道是否有可能完全使用交换内存或其他东西。只是这种记忆的转移花费了很多时间。 CPU 使用率急剧下降。

【问题讨论】:

  • 您的程序是否可能存在内存泄漏?我看到您使用内存泄漏运算符 (new) 而不是自动内存管理,所以也许您在很多其他地方也这样做了。
  • 也许减少线程数可以将内存消耗减少4倍?当问题从 cpu-bound 转移到 memory-bound 时,更多线程会降低性能。
  • 购买更多的内存也有帮助。
  • 垃圾收集器可能是个好主意。 @user2079303 取决于输入,更多的 RAM 也无济于事。
  • 垃圾收集器不是魔法。当您使用new 时,您必须使用delete

标签: c++ memory memory-management heap-memory


【解决方案1】:

如果您有 1 Gig 的 RAM,并且您有一个程序用完 2 Gb RAM,那么您将不得不寻找其他地方来存储多余的数据......显然。默认的操作系统方式是交换,但另一种方法是通过使用内存映射文件来管理您自己的“交换”。

您打开一个文件并在其中分配一个虚拟内存块,然后将文件的页面放入 RAM 中进行处理。操作系统在很大程度上会为您管理这些,但您应该考虑您的内存使用情况,因此如果可以的话,不要尝试在内存中保持对相同块的访问。

在 Windows 上使用 CreateFileMapping(),在 Linux 上使用 mmap(),在 Mac 上使用 mmap()

【讨论】:

  • 好的,谢谢,我会看看这个。实际上我正在使用mac OS,但我认为这也适用于mac。这不会以某种方式使访问变慢吗?
  • 是的,它使访问变慢 - 但你不能将一夸脱放入品脱罐中。除了购买更多 RAM,您还必须管理资源限制。
  • 是的,与 RAM 相比,它非常慢。但是创建使用比可用内存更多的算法是你的错。专注于减少内存消耗。
【解决方案2】:

操作系统工作正常 - 交换时它不区分堆栈和堆 - 它会为您分页您似乎没有使用的任何内容并加载您要求的任何内容。

您可以尝试以下几种方法:

  • 考虑是否可以将myType 变小 - 例如使用int8_t 甚至宽度适当的位域而不是int,使用指向池化字符串的指针而不是最坏情况长度的字符数组,在数组中使用比指针小的偏移量等。如果你向我们展示输入也许我们可以提出建议。

    • 考虑一下你的分页——如果你在一个内存页上有很多对象(可能是 4k),如果其中任何一个被使用,它们就需要留在内存中,所以尝试获取将在周围使用的对象同时到同一个内存页面 - 这可能涉及散列到相关myType 对象的小数组,或者如果可能的话甚至将所有数据移动到打包数组中(无论如何二进制搜索都可以很快)。简单使用的哈希表往往会占用内存,因为相似的对象被放入完全不相关的存储桶中。

    • 可以使用压缩进行序列化/反序列化:您可以主动将它们序列化为更紧凑的形式,然后仅在需要时才反序列化,而不是让操作系统换出全部 myType 内存。

  • 1234563到“B组”

现在更新您已经发布了您的实际数据类型...

遗憾的是,使用short 可能没有多大帮助,因为sizeof key 无论如何都需要为16 才能对齐double;如果您不需要精度,可以考虑float?另一种选择是创建一个单独的地图数组...

tbb::concurrent_hash_map<double,value> map[65536];

然后您可以索引到map[my_short][my_double]。它可能更好或更糟,但很容易尝试,因此您不妨进行基准测试....

对于cl_I,2 分钟的挖掘表明数据存储在 union 中 - 大概 word 用于小值和必要时的指针之一......这看起来是一个非常好的设计 - 很难改进。

如果数字倾向于重复很多(一个很大的 if),您可以尝试例如保留一个大的cl_Is 注册表,并双向映射到您将存储在My_Map::map 中的打包整数ID - 虽然很繁琐。解释一下,假设你得到 987123498723489 - 你在 vector&lt;cl_I&gt; 上得到 push_back 它,然后在 hash_map&lt;cl_I, int&gt; 中将 [987123498723489 设置为该索引(即 vector.size() - 1)。遇到新数字时继续前进。您始终可以使用vector 中的直接索引从int id 映射回cl_I,另一种方式是O(1) 摊销哈希表查找。

【讨论】:

  • 我添加了我的类型;已经考虑过这一点,并在std::pair 的第二部分使用了缩写。
  • @alexvii:听起来很难 - 我在答案中添加了一些。为了生成/遇到密钥,是否有任何特定模式?一些自然的方式知道各种cl_I会被依次访问?
猜你喜欢
  • 2015-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-15
  • 1970-01-01
  • 2017-09-02
  • 1970-01-01
相关资源
最近更新 更多