无法足够快地分配内存?

Posted

技术标签:

【中文标题】无法足够快地分配内存?【英文标题】:cannot allocate memory fast enough? 【发布时间】:2016-06-19 07:41:00 【问题描述】:

假设您的任务是解决应用程序中的性能瓶颈。通过分析,我们发现瓶颈与内存分配有关。我们发现应用程序每秒只能执行 N 次内存分配,无论我们有多少线程分配内存。为什么我们会看到这种行为,以及我们如何提高应用程序分配内存的速率。 (假设我们不能改变我们正在分配的内存块的大小。假设我们不能减少动态分配内存的使用。)

【问题讨论】:

这是在笔试中提出的。我还不知道他们想要什么。只是想看看你们在这里的想法。谢谢 多么愚蠢的问题。它创建了一个不可能的情况,问题是内存分配的固定速率,并询问如何提高内存分配的速率。你不能。解决方案很明显,通过减少对动态分配内存的需求或重新设计内存分配策略来解决问题。不幸的是,这个问题还告诉你,你不能做明显的解决方案。当然,这永远不会是真的。这根本不是现实世界。 @ter 您可能对这个问题是正确的,但对本网站的观点有误。通过Help Center,我们根据您在现实世界中面临的问题处理实际问题。 这很简单。在开始时分配内存。安全关键系统做到这一点 谢谢各位!我认为这是一个非常奇怪的问题,我自己有这些限制......答案很明显 - 像你们所说的那样预先分配,单独的堆。我不会因为他们问的废话类型而去这家公司! 【参考方案1】:

好的,存在一些解决方案 - 但是几乎所有解决方案似乎都被某种约束或其他限制排除在外。

1. 让更多线程分配内存

我们发现应用程序每秒只能执行 N 次内存分配,无论我们有多少线程分配内存。

由此,我们可以取消添加更多线程的任何想法(因为“不管有多少线程”...)。

2. 一次分配更多内存

假设我们不能改变我们正在分配的内存块的大小。

很明显,我们必须分配相同的块大小。

3. 使用(一些)静态内存

假设我们不能减少动态分配内存的使用。

我觉得这个最有趣.. 让我想起了我听说过一个关于 FORTRAN 程序员(在 Fortran 有动态内存分配之前)的故事,他只是使用分配在堆栈上的巨大静态数组作为私有堆。 不幸的是,这个约束阻止了我们使用这样的技巧。但是,它确实提供了一个(该)解决方案的一个方面。


我的解决方案

在执行开始时(无论是程序,还是基于每个线程)进行几次^ 内存分配系统调用。然后稍后在程序中使用这些内存(以及现有的动态内存分配)。

* 注意:“几个”可能是一个确切的数字,从您的分析中确定,问题一开始就提到了 /em>.

TL;DR

技巧是修改内存分配的时间

【讨论】:

有趣的想法。基本上,您只需预先分配一个本地堆并从中进行后续动态分配,而不是每次都返回操作系统。这可能会稍微快一些,因为它避免了系统调用的开销。但我也不确定它是否满足问题的要求。似乎违反了减少使用动态分配内存的禁令。我不确定如何从字面上理解。 我不认为它减少动态分配内存的使用,只是及时移动它。毕竟分配它是同一个系统调用。【参考方案2】:

看起来是一个很有挑战性的问题,虽然没有细节,你只能做一些猜测。 (这很可能是这个问题的想法)

这里的限制是分配的数量,而不是分配的大小。 如果我们可以假设您可以控制分配发生的位置,那么您可以一次为多个实例分配内存。请将以下代码视为伪代码,仅用于说明目的。

const static size_t NR_COMBINED_ALLOCATIONS = 16;
auto memoryBuffer = malloc(size_of(MyClass)*NR_COMBINED_ALLOCATIONS);
size_t nextIndex = 0;
// Some looping code
    auto myNewClass = new(memoryBuffer[nextIndex++]) MyClass;
    // Some code
    myNewClass->~MyClass();
free(memoryBuffer);

尽管您很可能会解决这个瓶颈,但您的代码很可能会变得更加复杂。如果你必须返回这个新类,你甚至需要更多的代码来做内存管理。

根据这些信息,您可以为您的 STL 编写自己的分配器实现,覆盖“new”和“delete”运算符...

如果这还不够,请尝试挑战限制。为什么只能进行固定数量的分配,这是因为唯一锁定吗?如果是这样,我们可以改进吗?为什么你需要这么多分配,改变正在使用的算法会解决这个问题吗...

【讨论】:

【参考方案3】:

...应用程序每秒只能执行 N 次内存分配, 无论我们有多少线程分配内存。为什么我们会 看到这种行为以及我们如何提高 应用程序可以分配内存。

恕我直言,最可能的原因是分配来自公共系统池。

因为它们共享一个池,所以每个线程都必须通过一些临界区阻塞机制(可能是信号量)来获得访问权限。

竞争动态内存的线程越多(即使用新的)将导致更多的临界区阻塞。

任务之间的上下文切换在这里是浪费时间。


如何提高利率?

选项 1 - 序列化使用...当然,这意味着您不能简单地尝试在另一个级别使用信号量。对于我工作的一个系统,在系统启动期间发生了高动态内存利用率。在这种情况下,最简单的方法是更改​​启动方式,使(该集合的)线程 n+1 仅在线程 n 完成初始化并进入其等待输入循环后才启动。一次只有 1 个线程启动它,(并且很少有其他动态内存用户正在运行)没有发生关键部分阻塞。 4 个同时启动需要 30 秒。 5 秒内完成 4 次序列化启动。

选项 2 - 为每个特定线程提供一个 ram 池和一个私有的 new/delete。如果一次只有一个线程访问池,则不需要临界区或信号量。在嵌入式系统中,这里的挑战是为线程分配合理数量的私有池,而不是过多的浪费。在具有数 GB 内存的桌面上,这可能不是问题。

【讨论】:

在 Ubuntu 15.10 (64) 上,使用 g++ v5.2.1,未优化 (-O0),使用 std::mutex 强制执行 std::thread 的上下文切换,每个需要 12,000 纳秒.另一方面,Mutex 方法调用可以在 42 纳秒内同时进行 lock() 和 unlock()。因此,如果没有竞争,互斥体的成本就会很低。【参考方案4】:

我相信您可以使用一个单独的线程来负责内存分配。该线程将有一个包含线程标识符映射和所需内存分配的队列。线程不会直接分配内存,而是向队列发送分配请求并进入等待状态。队列将尝试处理队列中每个请求的内存分配并唤醒相应的睡眠线程。当负责内存处理的线程由于限制无法处理分配时,它应该等待内存可以再次分配。

可以在解决方案中构建另一层,因为@Tersosauros 的解决方案建议稍微优化速度,但它应该基于上述想法。

【讨论】:

以上是关于无法足够快地分配内存?的主要内容,如果未能解决你的问题,请参考以下文章

python pymssql 错误:18456,b'DB-Lib 错误消息 20010,严重性 8:\n无法分配足够的内存

Java内存分配策略

分配内存

我可以确定重新分配更少的内存总能找到足够的内存吗?

BUPT复试专题—内存分配(2014-2)

分配内存malloc()和free()