单个 .neat 进程的内存限制为约 2.5 GB

Posted

技术标签:

【中文标题】单个 .neat 进程的内存限制为约 2.5 GB【英文标题】:Memory limitted to about 2.5 GB for single .net process 【发布时间】:2019-04-11 02:19:01 【问题描述】:

我正在编写在 Windows Server 2016 上运行的 .NET 应用程序,它对一大堆大文件执行 http 获取。这大大加快了下载过程,因为您可以并行下载它们。不幸的是,一旦它们被下载,需要相当长的时间才能将它们全部重新组合在一起。

有 2-4k 个文件需要合并。这将运行的服务器有足够的内存,接近800GB。我认为使用MemoryStreams 来存储下载的片段直到它们可以顺序写入磁盘是有意义的,但是在我得到一个之前我只能消耗大约2.5GB 的内存System.OutOfMemoryException 错误。服务器有数百 GB 可用,我不知道如何使用它们。

【问题讨论】:

ConcatenatedStream from How do I concatenate two System.Io.Stream instances into one? 可能会满足您的需求,只要您不需要随机搜索。 并且,请确保您已编译为 x64(或任何没有“首选 32 位标志”并在 x64 上运行的 CPU) 2.它是 x64,我使用 dumpbin 来验证是否支持大地址。 这是一个简单的程序来重现我的问题。 static void Main(string[] args) MemoryStream ms1 = new MemoryStream((int)Math.Pow(1024, 3)); MemoryStream ms2 = new MemoryStream((int)Math.Pow(1024, 3)); MemoryStream ms3 = new MemoryStream((int)(Math.Pow(1024, 3)*.95)); MemoryStream ms4 = new MemoryStream((int)Math.Pow(1024, 3)); //this errors out with an out of memory error 第三个内存流很有趣。我将允许的字节总数乘以 0.95,它会起作用,如果我达到 0.96 或更高,它将不起作用。如果第三个没有,第 4 个 MemoryStream 会导致内存不足错误。 【参考方案1】:

MemoryStreams 是围绕字节数组构建的。 Arrays cannot be larger than 2GB currently.

System.Array 的当前实现对其所有内部计数器等都使用 Int32,因此理论上的最大元素数为 Int32.MaxValue

Microsoft CLR 还规定了 2GB max-size-per-object 限制

当您尝试将内容放在单个 MemoryStream 中时,底层数组变得太大,因此出现异常。

尝试单独存储这些片段,并在准备好后将它们直接写入FileStream(或您使用的任何东西),而不是先尝试将它们全部连接到一个对象中。

【讨论】:

有一个应用程序设置允许创建大于 2GB 大小的数组。见docs.microsoft.com/en-us/dotnet/framework/configure-apps/… @ckuri 我相信使用它您仍然无法创建大于 2GB 的 byte 数组,因为它仍然受最大索引大小(即 UInt32 .MaxValue)。阅读备注部分:“对于字节数组,任何单个维度的最大索引为 2,147,483,591 (0x7FFFFFC7)……” 我试过了,你是对的。无法初始化长度大于 2^31 的数组。所以new long[2_000_000_000]成功创建了一个16GB的数组,但是new byte[3_000_000_000]抛出了OverflowException。 我尝试使用多个小于 2GB 的 MemoryStream 并将它们放入队列中。无论我使用什么大小,我都可以创建大约 2.5 GB 的空间,当我尝试创建另一个时,我会开始出现内存不足错误。 @JoshDayberry 那时你 99% 以 x86 运行它。我猜你的应用设置为 AnyCPU(首选 32 位)。这将使您的代码也作为 32 位程序集运行,请参见:***.com/a/12066861/10614791 将其显式设置为 x64(如果在 32 位系统上会彻底崩溃,则 AnyCPU 没有意义)并且它将起作用。我复制了你的问题,这样就解决了。【参考方案2】:

根据MemoryStream class 的源代码,您将无法将超过 2 GB 的数据存储到此类的一个实例中。 原因是流的最大长度设置为Int32.MaxValue,array 的最大索引设置为0x0x7FFFFFC7,即十进制的 2.147.783.591 (= 2 GB)。

片段内存流

private const int MemStreamMaxLength = Int32.MaxValue;

片段数组

// We impose limits on maximum array lenght in each dimension to allow efficient 
// implementation of advanced range check elimination in future.
// Keep in sync with vm\gcscan.cpp and HashHelpers.MaxPrimeArrayLength.
// The constants are defined in this method: inline SIZE_T MaxArrayLength(SIZE_T componentSize) from gcscan
// We have different max sizes for arrays with elements of size 1 for backwards compatibility
internal const int MaxArrayLength = 0X7FEFFFFF;
internal const int MaxByteArrayLength = 0x7FFFFFC7;

More than 2GB of managed memory 的问题很久以前就已经在微软论坛上讨论过了,并且可以参考那里的一篇关于BigArray, getting around the 2GB array size limit 的博客文章。

更新

我建议使用以下代码,它应该能够在 x64 构建上分配超过 4 GB 但在 x86 构建上会失败

private static void Main(string[] args)

    List<byte[]> data = new List<byte[]>();
    Random random = new Random();

    while (true)
    
        try
        
            var tmpArray = new byte[1024 * 1024];
            random.NextBytes(tmpArray);
            data.Add(tmpArray);
            Console.WriteLine($"data.Count MB allocated");
        
        catch
        
            Console.WriteLine("Further allocation failed.");
        
    

【讨论】:

我在流中存储的空间不超过 2GB。然而,我使用多个流,每个块一个,来保存数据。无论我制作的块大小如何,它似乎总是在 2.5GB 的内存使用量达到峰值时才会出现内存不足错误。我将许多 MemoryStreams 存储在一个队列中。 @JoshDayberry,我明白了......你的页面文件的配置是什么?我知道如果页面文件的大小配置错误,内存分配可能会出现一些问题。我刚刚搜索了这篇文章,又找到了:Pushing the limits of windows physical memory 和Pushing the limits of windows virtual memory。最后一个可能对你有帮助... 服务器有大约 800GB 的内存,我们卡在 3 左右。页面文件利用率稳定在 0%。为什么页面文件是相关的?【参考方案3】:

正如已经指出的那样,这里的主要问题是MemoryStream 的本质是由具有固定上限大小的byte[] 支持的。

已注意到使用替代Stream 实现的选项。另一种选择是研究“管道”,即新的 IO API。 “管道”基于不连续的内存,这意味着它不需要使用单个连续的缓冲区;管道库将根据需要分配多个平板,您的代码可以处理这些平板。我已经写了很多关于这个主题的文章;第 1 部分is here。第 3 部分可能最关注代码。

【讨论】:

【参考方案4】:

只是为了确认我理解您的问题:您正在以多个并行块下载一个非常大的文件,并且您知道最终文件有多大?如果你不这样做,那么这确实会变得有点复杂,但它仍然可以完成。

最好的选择可能是使用MemoryMappedFile (MMF)。您要做的是通过 MMF 创建目标文件。每个线程将创建该文件的视图访问器并并行写入。最后,关闭 MMF。这实质上为您提供了 MemoryStreams 所需的行为,但 Windows 通过磁盘支持文件。这种方法的好处之一是 Windows 会在后台管理将数据存储到磁盘(刷新),因此您不必这样做,并且应该会产生出色的性能。

【讨论】:

以上是关于单个 .neat 进程的内存限制为约 2.5 GB的主要内容,如果未能解决你的问题,请参考以下文章

Node.js(和 chrome V8)中的内存限制

Linux内存体系及文件系统

当 Windows 可以访问的总内存也限制为 4GB 时,Windows 如何将 4GB 地址空间分配给多个进程

Node内存限制与解决方案

Windows 上任何单个进程可以处理的最大内存量

如何限制单个 Linux 进程的内存使用而不杀死该进程