10

在我开始真正的问题之前,让我先说一下我可能会在这里弄错一些细节。如果是这样,请在这些问题上逮捕我,甚至不要回答我的问题。

我的问题基本上是关于 DLL 和 .NET。我们有一个使用大量内存的应用程序,我们正试图弄清楚如何正确测量这一点,尤其是当问题主要发生在客户的计算机上时。

让我印象深刻的一件事是,我们有一些相当大的 .NET 程序集,其中包含生成的 ORM 代码。

如果我使用具有唯一基地址的非托管 (Win32) DLL,同一台机器上的多个同时进程会将 DLL 加载到物理内存中,然后将其映射到所有应用程序的虚拟内存中。因此,该 DLL 将使用一次物理内存。

问题是 .NET 程序集会发生什么。这个 DLL 包含 IL,虽然它的这一部分可能在应用程序之间共享,但是由这个 IL 产生的 JITted 代码呢?是共享的吗?如果不是,我如何衡量以找出这实际上是否导致了问题?(是的,我知道,它会有所贡献,但在它成为最大的问题之前,我不会花太多时间在这上面)。

另外,我知道我们没有查看解决方案中所有 .NET 程序集的基地址,.NET 程序集是否有必要这样做?如果是这样,是否有一些关于如何确定这些地址的指南?

对此领域的任何见解都将受到欢迎,即使事实证明这不是一个大问题,甚至根本不是问题。


编辑:刚刚发现这个问题:.NET 程序集和 DLL rebase部分回答了我的问题,但我仍然想知道 JITted 代码是如何影响所有这些的。

从该问题及其接受的答案看来,JITted 代码放置在堆上,这意味着每个进程将加载共享的二进制程序集映像,并在其自己的内存空间内生成代码的私有 JITted 副本。

我们有什么方法可以衡量这个吗?如果这会产生大量代码,我们将不得不更多地查看生成的代码以确定是否需要对其进行调整。


编辑:在这里添加了一个较短的问题列表:

  1. 确保 .NET 程序集的基地址是唯一的且不重叠以避免重新设置主要用于将 IL 代码从 JITting 中提取出来的 dll 是否有任何意义?
  2. 如何测量 JITted 代码使用了多少内存来确定这是否真的是一个问题?

@Brian Rasmussen 在此处的回答表明,正如我所料,JITting 将生成 JITted 代码的每个进程的副本,但是重新设置程序集实际上会对减少内存使用产生影响。我将不得不深入研究他提到的 WinDbg+SoS 工具,我已经在我的列表中列出了一段时间,但现在我怀疑我不能再推迟了 :)


编辑:我在这个主题上找到了一些链接:

4

3 回答 3

6

这是针对问题 1)

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

如果您想了解有关从 WinDbg 获取此信息的更多详细信息,请告诉我。

这是问题2)

根据Expert .NET 2.0 IL Assembly一书,纯 IL PE 文件的.reloc一部分仅包含一个用于 CLR 启动存根的修复条目。因此,在变基期间托管 DLL 所需的修复量是相当有限的。

但是,如果您列出任何给定的托管进程,您会注意到 Microsoft 已重新设置其托管 DLL 的大部分(或全部)基础。是否应将其视为变基的原因取决于您。

于 2009-01-26T09:23:44.523 回答
3

我不确定以下信息对于较新版本的 .NET 和/或 Windows 版本有多准确。自 .NET 早期以来,MS 可能已经解决了一些 DLL 加载/共享问题。但我相信以下大部分内容仍然适用。

使用 .NET 程序集,进程之间(以及终端服务器会话之间)页面共享的许多好处消失了,因为 JIT 需要动态编写本机代码 - 没有图像文件来备份本机代码。因此,每个进程都有自己的、单独的内存页面用于 jitted 代码。

这类似于由于 DLL 的基础不正确而导致的问题 - 如果操作系统在加载标准 Win32 DLL 时需要对其执行修复,则无法共享已修复部分的内存页面。

然而,即使无法共享 jitted 代码,重新定位 .NET DLL 也有好处,因为仍然会为元数据(和 IL)加载 DLL - 如果不需要修复,则可以共享这些内容。

使用 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

于 2009-01-26T09:34:50.413 回答
0

我认为您对共享程序集和 dll 以及进程内存空间感到困惑。

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

问题是您似乎期望库调用分配的内存也可以共享这永远不会(几乎)发生。当你的库中的一个函数分配内存时,我猜这对于 ORM DLL 发生了很多,该内存是在调用进程的内存空间内分配的,每个进程都有唯一的数据实例。

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

编辑: 好的,让我们看看 JIT 如何与 .NET 程序集一起工作。

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

于 2009-01-26T09:08:39.093 回答