【问题标题】:Can Win32 "move" heap-allocated memory?Win32 可以“移动”堆分配的内存吗?
【发布时间】:2011-01-07 00:03:03
【问题描述】:

我有一个 .NET/native C++ 应用程序。目前,C++ 代码在默认堆上分配内存,该堆在应用程序的整个生命周期内都存在。基本上,函数/命令在 C++ 中执行,这会导致当前持久内存的分配/修改。我正在研究一种在执行过程中取消其中一个功能/命令的方法。我们有数百个这样的命令,其中许多是非常复杂的(遗留)代码。

我试图避免的蛮力方法是修改每个命令/函数以检查取消并进行所有适当的清理(释放堆内存)。我正在研究一种多线程方法,其中一个附加线程接收取消请求并终止命令执行线程。我希望使用HeapCreate()(Win32)在“私有堆”上分配所有动态内存。这样,处理取消请求的线程可以破坏私有堆。但是,如果命令运行完成,我需要动态内存来保持。在这种情况下,我想在逻辑上相当于将私有堆内存“移动”到默认/进程堆,而不会产生实际副本的成本。这有可能吗?这有意义吗?

另外,我认识到我可以为每个命令/函数执行创建一个新的私有堆(每个都将是一个新线程)。如果命令被取消,私有堆可能会被破坏,或者如果命令完成,它将继续存在。 number 堆无限增长有什么问题吗?我知道每个堆都涉及一些开销。我可能会遇到什么限制?

我在 8GB RAM 的 64 位 Windows 7 上运行(将此视为目标平台)。我正在使用的应用程序大约有 100 万个 SLOC(一半 C++,一半 C#)。我正在寻找有关私有堆管理的任何经验/建议,或者只是我的解决方案的替代方案。

【问题讨论】:

  • 您是在问,您在一个堆中分配的内存是否可以在不实际复制数据的情况下“重新分配”到另一个堆?
  • 我有 92% 的把握答案是否定的。但不足以将其发布为答案。
  • 堆内存分配并不是强制终止线程的唯一问题。终止线程的唯一安全方法是从内部。这样,您就知道线程不在某个锁定操作的中间。可中断性需要成为函数的内在特征;这不是你可以从外面固定的东西。

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


【解决方案1】:

堆是一大块内存。它是一个用户级内存管理器。堆由较低级别的系统内存调用创建(例如,Linux 中的 sbrk 和 Windows 中的 VirtualAlloc)。在一个堆中,你可以通过 malloc/new/free/delete 请求或返回一小块内存。默认情况下,一个进程只有一个堆(与堆栈不同,所有线程共享一个堆)。但是,你可以有很多堆。

  • 是否可以合并两个没有复制的堆? 堆本质上是一种数据结构,用于维护已使用和已释放内存块的列表。因此,堆应该有一种称为元数据的簿记数据。当然,这个元数据是每个堆的。 AFAIK,没有堆管理器支持两个堆的合并操作。我已经查看了 Linux glibc (Doug Lea's implementation) 中 malloc 实现的整个源代码,但没有这样的操作。 Windows Heap* 函数也以类似的方式实现。 因此,目前无法移动或合并两个单独的堆。

  • 有可能有很多堆吗?我不认为有很多堆应该有什么大问题。正如我之前所说,堆只是一种数据结构,用于保存已使用/已释放的内存块。所以,应该有一些开销。但是,没那么严重。当您查看malloc implementation 之一时,有malloc_state,这是每个堆的基本数据结构。例如,您可以通过create_mspace(在Windows 中为HeapCreate)创建另一个堆,然后您将获得一个新的malloc 状态。它不是那么大。因此,如果这种过渡(一些堆开销与实现容易性)没问题,那么您可以继续。

如果我是你,我会尝试你描述的方式。对于我,这说得通。拥有大量堆对象不会产生很大的开销。

另外,应该注意的是,从技术上讲,移动内存区域是不可能的。指向移动内存区域的指针将导致悬空指针。

附言您的问题似乎是一个事务,尤其是软件事务内存。 STM 的典型实现会缓冲待处理的内存写入,然后在事务没有冲突的情况下提交到真实系统内存。

【讨论】:

  • @STingRaySC:虚拟地址和物理地址的映射与这个问题无关。我们只是看不到这样的映射。我们只考虑虚拟地址空间中的移动/合并。程序员只能看到虚拟地址(一些嵌入式或实模式情况除外)。是的,到物理地址的映射总是可以改变的,但这完全是隐藏的。然而,移动/合并黑白堆仅在虚拟地址空间内完成,因此,除非我们实现非常昂贵的机制来追踪引用的指针,否则通常是不可能的。
【解决方案2】:

来自 MSDN 的 Heap Functions 页面: “HeapAlloc分配的内存是不可移动的。HeapAlloc返回的地址在内存块被释放或重新分配之前都是有效的;内存块不需要加锁。”

您能否根据自己的 malloc() 实现重新链接旧版应用程序?如果是这样,您应该能够在不修改其余代码的情况下进行管理。您的自定义 malloc 库可以按线程跟踪分配的块,并有一个“FreeAllByThreadId() 函数,您可以在杀死遗留函数的线程后调用该函数。您可以在库中使用私有堆。

私有堆的替代方案可能是从内存映射文件中进行自己的分配。请参阅“Creating Named Shared Memory”。您在为遗留线程初始化分配库时创建共享内存。成功后,将其映射到主线程,以便您的 c# 可以访问它;终止时,关闭它并释放到系统中。

【讨论】:

    【解决方案3】:

    使用单独的进程而不是单独的线程可能会更好:

    • 使用内存映射文件(即根本不是文件 - 只是交叉处理的共享内存)
    • 杀死一个进程比杀死一个线程“干净”
    • 认为你可以让共享内存在不移动的情况下“存活”下来 - 你映射/取消映射而不是移动

    虽然您可能需要自己进行一些内存管理。

    无论如何,值得研究。我正在研究将进程间内存用于其他一些事情,它有一些不寻常的属性(可以清楚地回忆起所有这些,那是不久前的事了),你也许可以利用它。

    只是一个想法!

    【讨论】:

      【解决方案4】:

      没有。内存不能在堆之间移动。

      【讨论】:

        猜你喜欢
        • 2013-10-18
        • 2017-11-28
        • 1970-01-01
        • 2013-01-14
        • 2019-05-17
        • 2014-08-14
        • 2013-04-04
        • 1970-01-01
        • 2017-05-16
        相关资源
        最近更新 更多