DLLs,内存映射,基地址,内存使用和.NET?

10
在我开始正式提问之前,让我说一下,有些细节可能会出错。如果有的话,请纠正我,而不是回答我的问题。
我的问题基本上与DLL和.NET有关。我们有一个应用程序正在使用相当多的内存,我们正在尝试弄清楚如何正确地测量它,特别是当问题主要出现在客户机上时。
有一件事情使我想到,那就是我们有一些相当大的带有生成的ORM代码的.NET程序集。
如果我使用一个具有唯一基地址的未托管(Win32)DLL,则同一台机器上的多个并行进程将把DLL加载到物理内存中一次,并将其映射到虚拟内存中供所有应用程序使用。因此,物理内存只会用于此DLL一次。
问题是.NET程序集会发生什么情况。这个DLL包含IL,虽然其中的部分可能在应用程序之间共享,但是由此产生的JITted代码怎么样?它是共享的吗?如果没有,我该如何衡量以确定它是否真正导致问题或不是? (是的,我知道,它会起作用,但在它成为最大的问题之前,我不会花太多时间在这上面)。
此外,我知道我们还没有查看解决方案中所有.NET程序集的基地址,对于.NET程序集来说,这是必要的吗?如果是这样,是否有一些指导方针可用于确定这些地址?
任何关于此领域的见解都将非常受欢迎,即使最终证明这不是一个大问题,甚至根本不是问题。
编辑:刚刚发现这个问题:.NET程序集和DLL调整基址部分回答了我的问题,但我仍然想知道JITted代码如何影响所有这些。
从那个问题及其被接受的答案中可以看出,JITted代码放置在堆上,这意味着每个进程将加载共享二进制程序集图像,并在其自己的内存空间中生成私有的JITted代码副本。

有没有办法可以测量这个呢?如果生成的代码很多,我们就需要更仔细地查看生成的代码,以确定是否需要调整它。


编辑:在此添加一些较短的问题:

  1. 确保.NET程序集的基地址唯一且不重叠,以避免将大部分用于获取JIT编译的IL代码的dll重新定位,是否有意义?
  2. 如何测量用于JIT编译的代码使用了多少内存,以确定这是否真的是一个问题?

@Brian Rasmussen这里回答说,像我预期的那样,JIT会产生每个进程副本的JIT编译的代码。但重新定位程序集实际上会对减少内存使用有影响。我将不得不深入WinDbg+SoS工具中,这是我已经列在清单上一段时间的事情,但现在我怀疑我再也不能拖延了 :)


编辑:我在这方面找到了一些链接:

3个回答

6

问题1)

Jitted代码放在特殊的堆上。您可以使用WinDbg + SoS中的!eeheap命令检查此堆。因此,每个进程都将拥有其自己的jitted代码副本。该命令还将显示代码堆的总大小。

如果您需要了解有关从WinDbg获取此信息的其他详细信息,请告诉我。

问题2)

根据书籍Expert .NET 2.0 IL Assembly,纯IL PE文件的.reloc部分仅包含CLR启动程序的一个修复项。因此,在重新定位期间,托管DLL所需的修复项数量相当有限。

然而,如果您列出任何给定的托管过程,您会注意到Microsoft已经重新定位了它们的大部分(或可能全部)托管DLL。这是否应视为重新定位的原因取决于您。


好的,这看起来很有前途,至少我们可以得到一些数据。谢谢! - Lasse V. Karlsen
我认为,基于这里其他答案的内容,我需要进行变基操作,即使只是为了避免使用私有副本的dll,而不是共享映射引用。感谢您的信息! - Lasse V. Karlsen
希望你能看到这个问题,Brian。请问你是否知道是否有一个在线网站可以购买这本书的PDF版本?我只找到了一个提供密码保护文件的APRESS网站,这听起来很麻烦,我更喜欢一个数字签名到我的Adobe账户上的版本... - Lasse V. Karlsen
我有一份实体复印件,但我相信也有一个PDF版本可用。我记得在后面看到过一个“仅获取PDF”的选项。等我回家再确认一下(在工作时没有这本书)。 - Brian Rasmussen
我早该猜到了。这本书可以在Apress电子版上获取,所以我想你已经了解了最新情况。然而,我发现一份新闻稿称Apress图书可以在Safari上获取,这可能是另一个选择。 - Brian Rasmussen

3
我不确定以下信息在新版本的.NET和/或Windows版本中是否准确。自.NET早期以来,微软可能已经解决了一些DLL加载/共享问题。但我相信以下大部分仍然适用。
由于JIT需要即时编译本机代码,没有映像文件来支持本机代码,因此使用.NET程序集时,许多进程(以及终端服务器会话之间)之间页面共享的好处消失了。因此,每个进程都有自己单独的内存页面用于jitted代码。
这类似于由于错误基础的DLL所引起的问题 - 如果操作系统在加载标准Win32 DLL时需要执行修复,则无法共享修复部分的内存页面。
但是,即使jitted代码不能共享,重新定位.NET DLL仍然有好处,因为该DLL仍然会为元数据(和IL)加载 - 如果不需要任何修复,则可以共享该内容。
可以通过使用ngen来帮助共享.NET程序集的内存页面,但这也带来了自己的一系列问题。
请参阅Jason Zander的这篇旧博客文章以获取详细信息:

http://blogs.msdn.com/jasonz/archive/2003/09/24/53574.aspx

Larry Osterman在他的博客文章中谈到了DLL页面共享以及修复效果的问题。

http://blogs.msdn.com/larryosterman/archive/2004/07/06/174516.aspx


我会查看那些链接,ngen是我们列出的要探索的事物清单中的一个选项,如果这证明是一个问题。谢谢! - Lasse V. Karlsen

0

我觉得你对共享程序集和dll以及进程内存空间有些混淆了。

.NET和标准Win32 DLL都会在使用它们的不同进程之间共享代码。在.NET中,这仅适用于具有相同版本签名的DLL,因此可以同时加载同一DLL的两个不同版本到内存中。

问题是看起来你期望库调用分配的内存也被共享,但实际上这几乎从不发生。当你的库内部函数分配内存时(我猜ORM DLL经常这样做),该内存分配在调用进程的内存空间内,每个进程都有数据的唯一实例。

所以,实际上DLL的代码只被加载一次并在调用者之间共享,但代码指令(因此是分配)在调用进程空间中单独进行。

编辑: 好的,让我们看看JIT如何处理.NET程序集。

当我们谈到JIT编译代码时,这个过程相对简单。内部有一个叫做虚方法表的结构,它基本上包含了在调用期间将被调用的虚拟地址。在.NET中,JIT通过基本编辑该表来使每个单独的调用重定向到JIT编译器。这样,每次我们调用一个方法时,JIT就会介入并将代码编译成实际的机器指令(因此是即时编译),一旦完成,JIT就会返回到VMT并替换旧的条目,以调用生成的低级别代码。这样,所有后续的调用都将重定向到已编译的代码(因此我们只需要编译一次)。因此,JIT不会每次都被调用,所有后续的调用都将重定向到相同的已编译代码。对于DLL,该过程可能是相同的(尽管我不能完全保证它是这样的)。


但是他们是否共享代码呢?我认为他们将共享IL指令,因为这些指令将作为DLL二进制映像的一部分加载,但该代码必须在执行之前进行JIT编译,这种方式是否共享?如果您已经回答了这个问题,请原谅我没有理解您的答案。 - Lasse V. Karlsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接