.NET程序集中的嵌入式资源在运行时是从磁盘还是内存加载的?

23

当我使用GetManifestResourceStream从.NET程序集中检索嵌入式资源时,会涉及什么样的I/O操作?

我看到两种可能性:

  1. .NET加载程序集时已将整个程序集放入内存中,因此GetManifestResourceStream只是访问内存。

  2. 只有当.NET加载程序集时,代码部分才被放入内存,因此GetManifestResourceStream需要返回到.dll文件以提取嵌入的资源。

我很确定第一种情况是正确的,特别是因为可以使用Assembly.Load(Byte[])从原始数据以动态方式加载程序集。但是如果嵌入了非常大的文件(比如几个GB),第二种可能性可能更有效。大小是否有影响?

只是挑战一些长期以来的假设,没有找到太多相关参考资料。


1
我不知道,但是_千兆字节_……?你确定没有比嵌入资源更好的存储和分发内容的方法吗? - KristoferA
1
当然 - 那只是一个假设。我在想,要么有最大资源大小的限制,要么在加载如此大的东西时会有一些分支。 - Matt Johnson-Pint
我对.NET一无所知,但通常现代操作系统会允许您将文件虚拟映射到内存中,然后在访问该内存地址时获取其中的特定部分。因此,它只加载被访问的块,同时虚拟地假装它已完全加载到内存中,在您的情况下占用了"千兆字节"的虚拟地址空间(在32位平台上可能会有问题!...在64位上可能不是什么大问题)。 - Ped7g
1
要明确的是,我通常嵌入的资源大小在几kb或者1MB左右。我只是好奇I/O性能是否会有所不同,特别是在处理大文件时是否会有所变化。 - Matt Johnson-Pint
根据被接受的答案,语句“我非常确定第一种情况是正确的”实际上是错误的吗? - joe
1个回答

30
在Windows、Linux和macOS等需求分页的虚拟内存操作系统上,“内存”这个术语并不精确。CLR使用内存映射文件(MMF)将程序集映射到进程的地址空间中。对于处理器来说,每4096字节有一个数字。此时还没有从文件中读取任何内容。
直到程序试图从地址空间内的地址读取数据,才会延迟读取。第一次访问会导致页面错误,内核为该页面分配RAM并填充文件内容。之后程序会继续运行,就好像什么都没有发生过。这强化了虚拟内存的“你不用付费用你不使用”的优势。
不存在“提取”,你是直接从内存中读取资源数据,这是最有效的实现方式。嵌入式资源在文件中的元数据和MSIL等其他数据一样,没有任何不同的行为。您也不必为您从未调用的程序集中的任何代码付费。
请记住,嵌入式资源占用与GC堆相同的操作系统资源,也需要地址空间。唯一的区别是,GC堆地址空间由操作系统的分页文件支持,且永远无法与其他进程共享,而程序集数据由程序集文件支持,可以共享。大型资源特别是缩小了.NET程序中可以分配的内存量,即使您从未使用它们。这只在32位进程中有所影响,在64位进程中有许多TB的地址空间。
另一个限制是,即使在64位进程中,MMF视图也不能超过2 GB,这为资源的最大大小设定了硬上限。通常会很早崩溃,导致生成CS1566失败,“指定参数超出了有效值范围”。顺便说一句,这不是一个很好的诊断信息。

那么嵌入式资源在程序运行期间只会一直停留在堆中吗?我原以为它被编译到可执行文件中,然后每当需要该资源时,它会从磁盘引用该内存并将其复制出来,但我想这并不比从磁盘读取文件更有效率...我想我总是认为资源用于避免频繁引用和管理的文件(例如标志),而不是作为性能提升器。 - Trevor Hart
不,堆栈与此无关,它仅占用地址空间。MMF的优点是你不必从文件中显式地读取它,不需要在分页文件中占用空间,会被自动缓存,因此任何后续访问都非常便宜,并且可以在其他进程需要RAM时简单地丢弃。需求分页虚拟内存操作系统的优点,它们都被充分利用了。 - Hans Passant

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