堆碎片和 Windows 内存管理器
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1684004/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Heap fragmentation and windows memory manager
提问by Budric
I'm having trouble with memory fragmentation in my program and not being able to allocate very large memory blocks after a while. I've read the related posts on this forum - mainly thisone. And I still have some questions.
我在程序中遇到内存碎片问题,一段时间后无法分配非常大的内存块。我已经阅读了这个论坛上的相关帖子——主要是这个。我还有一些问题。
I've been using a memory space profilerto get a picture of the memory. I wrote a 1 line program that contains cin >> var; and took a picture of the memory:
我一直在使用内存空间分析器来获取内存图片。我写了一个包含 cin >> var 的 1 行程序;并拍了一张记忆的照片:
alt text http://img22.imageshack.us/img22/6808/memoryk.gifWhere on the top arc - green indicates empty space, yellow allocated, red commited. My question is what is that allocated memory on the right? Is it the stack for the main thread? This memory isn't going to be freed and it splits the continuous memory that I need. In this simple 1 line program the split isn't as bad. My actual program has more stuff allocated right in the middle of the address space, and I don't know where it's comming from. I'm not allocating that memory yet.
alt 文本 http://img22.imageshack.us/img22/6808/memoryk.gif顶部圆弧的位置 - 绿色表示空白,黄色已分配,红色已提交。我的问题是右边分配的内存是什么?它是主线程的堆栈吗?该内存不会被释放,它会拆分我需要的连续内存。在这个简单的 1 行程序中,拆分并没有那么糟糕。我的实际程序在地址空间的中间分配了更多的东西,我不知道它来自哪里。我还没有分配那个内存。
How can I try solve this? I was thinking of switching to something like nedmalloc or dlmalloc. However that would only apply to the objects I allocate explicitly myself, whereas the split shown in the picture wouldn't go away? Or is there a way to replace the CRT allocation with another memory manager?
Speaking of objects, are there any wrappers for nedmalloc for c++ so I can use new and delete to allocate objects?
我该如何尝试解决这个问题?我正在考虑切换到 nedmalloc 或 dlmalloc 之类的东西。但是,这只适用于我自己明确分配的对象,而图片中显示的拆分不会消失?或者有没有办法用另一个内存管理器替换 CRT 分配?
说到对象,是否有任何用于 c++ 的 nedmalloc 的包装器,以便我可以使用 new 和 delete 来分配对象?
Thanks.
谢谢。
回答by CB Bailey
First, thank you for using my tool. I hope you find it useful and feel free to submit feature requests or contributions.
首先,感谢您使用我的工具。我希望你觉得它有用,并随时提交功能请求或贡献。
Typically, thin slices at fixed points in the address space are caused by linked dlls loading at their preferred address. The ones that load high up in the address space tend to be Microsoft operating system dlls. It's more efficient for the operating system if these can all be loaded at their preferred addresses because then the read-only parts of the dlls can all be shared between processes.
通常,地址空间中固定点的薄片是由链接的 dll 加载到其首选地址引起的。在地址空间中加载较高的往往是 Microsoft 操作系统 dll。如果这些都可以在它们的首选地址上加载,那么操作系统的效率会更高,因为这样 dll 的只读部分就可以在进程之间共享。
The slice that you can see is nothing to worry about, it barely cuts anything out of your address space. As you've noted, there are dlls, though, which load at other points in the address space. IIRC shlwapi.dll
is a particularly bad example, loading at about 0x2000000 (again IIRC) which often splits a large portion of the available address space into two smaller pieces. The problem with this is that once the DLL is loaded, there is nothing that you can do to move this allocate space around.
您可以看到的切片无需担心,它几乎不会从您的地址空间中切出任何内容。正如您所指出的,有 dll 会在地址空间的其他点加载。IIRCshlwapi.dll
是一个特别糟糕的例子,在大约 0x2000000(再次 IIRC)处加载,这通常将大部分可用地址空间分成两个较小的部分。这样做的问题是,一旦加载了 DLL,您就无法移动此分配空间。
If you link against the DLL (either directly or via another DLL), there is nothing that you can do. If you use LoadLibrary
you can get sneaky and reserve its preferred address, forcing it to be relocated - frequently somewhere better in the address space - before releasing that reserved memory. This doesn't always work, though.
如果您链接到 DLL(直接或通过另一个 DLL),则您无能为力。如果你使用LoadLibrary
你可以偷偷地保留它的首选地址,迫使它在释放保留的内存之前被重新定位——通常是地址空间中更好的某个地方。但是,这并不总是有效。
Under the hood, Address Space Monitor uses VirtualQueryEx
to examine the address space of the process but there is another call from the psapi library which other tools use (e.g. Process Explorer) which can show you which files (including DLLs) are mapped into which parts of the address space.
在VirtualQueryEx
幕后,地址空间监视器用于检查进程的地址空间,但还有来自 psapi 库的另一个调用,其他工具(例如进程资源管理器)使用它可以显示哪些文件(包括 DLL)被映射到进程的哪些部分地址空间。
As you've found, it can be scarily easy to run out of room in a 2GB user address space. Fundamentally, you're best defence against memory fragmentation is simply to not require any large contiguous blocks of memory. Although difficult to retro-fit, designing your applicationg to work with 'medium sized' chunks usually makes substantially more efficient usage of the address space.
正如您所发现的,在 2GB 的用户地址空间中用完空间非常容易。从根本上说,防止内存碎片的最好方法就是不需要任何大的连续内存块。虽然难以改造,但设计您的应用程序以使用“中等大小”的块通常可以显着提高地址空间的使用效率。
Similarly you can use a paging strategy, possibly using memory mapped files or Address Windowing Extensions.
同样,您可以使用分页策略,可能使用内存映射文件或地址窗口扩展。
回答by Timo Geusch
I assume that you are frequently allocating and deallocating objects of varying sizes and that this is what leads to your memory fragmentation issues?
我假设您经常分配和释放不同大小的对象,这就是导致您的内存碎片问题的原因?
There are various strategies to work around these; the different memory managers you mentioned might help if they can solve the fragmentation issue for you but that would require a little more analysis of the underlying causes of the fragmentation. For example, if you frequently allocate objects of three or four types and these tend to worsen the memory fragmentation issue, you might want to put those into their own memory pools to enable the reuse of memory blocks of the correct size. That way, you should have a set of memory blocks available that fit this particular object and prevent the common scenario that allocation of object X split a memory block that's big enough to hold Y in such a way that you suddenly can't allocate any Ys anymore.
有多种策略可以解决这些问题;如果您提到的不同内存管理器可以为您解决碎片问题,它们可能会有所帮助,但这需要对碎片的根本原因进行更多分析。例如,如果您经常分配三种或四种类型的对象,并且这些对象往往会加剧内存碎片问题,那么您可能希望将它们放入自己的内存池中,以启用正确大小的内存块的重用。那样的话,你应该有一组适合这个特定对象的可用内存块,并防止对象 X 的分配分割一个足够大的内存块以容纳 Y 的常见情况,以至于你突然无法分配任何 Ys了。
As to (2), I'm not aware of a wrapper around nedmalloc (frankly, I'm not very familiar with nedmalloc) but you can create your own wrappers very easily as you can either create class-specific operators new and delete or even overload/replace the global operators new and delete. I'm not a big fan of the latter but if your allocation "hotspot" consists of a handful of classes, it's usually pretty easy to retrofit them with their own, class-specific operators new and delete.
至于 (2),我不知道 nedmalloc 周围的包装器(坦率地说,我对 nedmalloc 不是很熟悉)但是您可以非常轻松地创建自己的包装器,因为您可以创建特定于类的运算符 new 和 delete 或甚至重载/替换全局运算符 new 和 delete。我不是后者的忠实粉丝,但如果您的分配“热点”由少数类组成,通常很容易用它们自己的、特定于类的运算符 new 和 delete 来改造它们。
That said, nedmalloc bills itself as a replacement for the standard malloc/free and at least with the MS compilers, I think the C++ runtime library will forward new/delete to malloc/free so it might well just be a case of building your executable with nedmalloc.
也就是说,nedmalloc 将自己标榜为标准 malloc/free 的替代品,至少在 MS 编译器中,我认为 C++ 运行时库会将 new/delete 转发到 malloc/free,因此它很可能只是构建可执行文件的一种情况使用 nedmalloc。
回答by Alan
In order to reduce memory fragmentation, you could take advantage of the Windows Low-Fragmentation Heap. We've used this in our product to good effect and haven't had nearly as many memory related problems since doing so.
为了减少内存碎片,您可以利用Windows Low-Fragmentation Heap。我们已经在我们的产品中使用了它,效果很好,并且自从这样做以来几乎没有遇到与内存相关的问题。
回答by Andrew Sandoval
The best way to find out where memory is allocated in your program is to use a debugger. There are allocations for every loaded DLL and the executable itself, and all of them fragment virtual memory. Additionally, using C/C++ libraries and Windows APIs will cause a heap to be created in your application, which will at very least reserve a chunk of virtual memory.
找出程序中内存分配位置的最佳方法是使用调试器。每个加载的 DLL 和可执行文件本身都有分配,所有这些都占用了虚拟内存。此外,使用 C/C++ 库和 Windows API 会导致在您的应用程序中创建一个堆,这至少会保留一块虚拟内存。
You could for example use VirtualAlloc to reserve a large chunck of virtual memory in a relatively small program, only to find that either the VirtualAlloc fails, or the application fails later when it tries to load a new DLL (etc.) You also can't always control what DLLs will be loaded and where. Many A/V and other products will inject DLLs into all running processes as they start. When this happens, those DLLs often have first pick at load addresses -- that is, their compiled/linked in default will likely be granted. Out of the available 2GB virtual address space of a typical 32-bit Windows application, if a DLL loads smack in the middle of that address space, the largest single allocation/reservation you can acquire will be less 1 GB.
例如,您可以使用 VirtualAlloc 在相对较小的程序中保留大量虚拟内存,结果却发现 VirtualAlloc 失败,或者应用程序稍后在尝试加载新 DLL 时失败(等)您也可以' t 始终控制将加载哪些 DLL 以及在何处加载。许多 A/V 和其他产品会在所有正在运行的进程启动时将 DLL 注入它们。发生这种情况时,这些 DLL 通常会首先选择加载地址——也就是说,它们的默认编译/链接可能会被授予。在典型的 32 位 Windows 应用程序的可用 2GB 虚拟地址空间中,如果 DLL 在该地址空间的中间加载,则您可以获得的最大单个分配/保留将少于 1 GB。
If you use windbg, you can see which regions of memory are consumed, reserved, etc. The lm command will show you the load addresses of all DLLs and the EXE and their range. The !vadump command will show you all of the virtual memory used by the process and the page protections. The page protections are a big hint into what is there. For example in the following (partial) !vadump from a 64-bit calc.exe process, you will see that the first region is simply a range of virtual memory protected from access. (Among other things this keeps you from allocating memory at address 0.) MEM_COMMIT means that memory is backed by RAM or the paging file. PAGE_READWRITE is possibly heap memory, or the data segment of a loaded module. PAGE_READEXECUTE is usually code that is loaded and that will show up in the list produced by lm. MEM_RESERVE means something has called VirtualAlloc to reserve a region of memory, but that it isn't mapped by the Virtual Memory Manager, and so forth...
如果您使用windbg,您可以看到哪些内存区域被消耗、保留等。lm 命令将显示所有DLL 和EXE 的加载地址及其范围。!vadump 命令将显示进程使用的所有虚拟内存和页面保护。页面保护是对那里内容的一个重要提示。例如,在以下(部分)来自 64 位 calc.exe 进程的 !vadump 中,您将看到第一个区域只是一个受保护而无法访问的虚拟内存范围。(除其他外,这会阻止您在地址 0 分配内存。) MEM_COMMIT 意味着内存由 RAM 或分页文件支持。PAGE_READWRITE 可能是堆内存,或加载模块的数据段。PAGE_READEXECUTE 通常是加载的代码,它将显示在 lm 生成的列表中。
0:004> !vadump
BaseAddress: 0000000000000000
RegionSize: 0000000000010000
State: 00010000 MEM_FREE
Protect: 00000001 PAGE_NOACCESS
BaseAddress: 0000000000010000
RegionSize: 0000000000010000
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00040000 MEM_MAPPED
BaseAddress: 0000000000020000
RegionSize: 0000000000003000
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Type: 00040000 MEM_MAPPED
I hope that helps explain things. Windbg is a great tool and has many extensions to help you find where memory is used.
我希望这有助于解释事情。Windbg 是一个很棒的工具,它有许多扩展可以帮助您找到使用内存的位置。
If you really care just about the heap, look at !heap.
如果您真的只关心堆,请查看 !heap。
回答by Goz
Could it be the executable? It has to be loaded into the address space somewhere ....
它可能是可执行文件吗?它必须加载到某个地址空间中......
As for 2 its pretty easy to override the global new and delete functions ... just define them.
至于 2,它很容易覆盖全局 new 和 delete 函数......只需定义它们。