大数组如何分配内存?

How a big array allocates memory?

我正在寻找一种将大型 3d 稀疏数组结构保存到内存中而不浪费大量内存的方法。在这里,我用多头数组做了一个实验:

using System;
using System.Diagnostics;
using System.Runtime;

namespace ConsoleApp4
{
    public class Program
    {
        static Process proc = Process.GetCurrentProcess();
        const int MB = 1024 * 1024;
        const int IMAX = 5;
        const int JMAX = 100000000;
        public static void ShowTextWithMemAlloc(string text)
        {
            proc.Refresh();
            Console.WriteLine($"{text,-30}WS64:{proc.WorkingSet64/MB,5}MB  PMS64:{proc.PrivateMemorySize64/MB,5}MB");
            Console.ReadKey();
        }
        public static void Main(string[] args)
        {
            Console.Write(" ");
            ShowTextWithMemAlloc("Start.");
            long[] lArray = new long[IMAX * JMAX];
            long[] l1Array = new long[IMAX * JMAX];
            long[] l2Array = new long[IMAX * JMAX];
            long[] l3Array = new long[IMAX * JMAX];
            ShowTextWithMemAlloc("Arrays created.");
            lArray[IMAX * JMAX - 1] = 5000;
            l1Array[IMAX * JMAX - 1] = 5000;
            l2Array[IMAX * JMAX - 1] = 5000;
            l3Array[IMAX * JMAX - 1] = 5000;
            ShowTextWithMemAlloc("Last elements accessed.");
            for (var i=IMAX-1; i>= 0; i--)
            {
                for (var j=0; j<JMAX; j++)
                {
                    lArray[i * JMAX + j] = i * JMAX + j;
                }
                ShowTextWithMemAlloc($"Value for row {i} assigned.");
            }
            //lArray = new long[5];
            //l1Array = null;
            //l2Array = null;
            //l3Array = null;
            //GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
            //GC.Collect();
            //ShowTextWithMemAlloc($"GC.Collect done.");
            ShowTextWithMemAlloc("Stop.");
        }
    }
}

如果要测试它,请将 COMPlus_gcAllowVeryLargeObjects 环境变量(项目属性 -> 调试)设置为 1 或更改 JMAX。这是输出:

 Start.                        WS64:   14MB  PMS64:    8MB
 Arrays created.               WS64:   15MB  PMS64:15360MB
 Last elements accessed.       WS64:   15MB  PMS64:15360MB
 Value for row 4 assigned.     WS64:  779MB  PMS64:15360MB
 Value for row 3 assigned.     WS64: 1542MB  PMS64:15360MB
 Value for row 2 assigned.     WS64: 2305MB  PMS64:15361MB
 Value for row 1 assigned.     WS64: 3069MB  PMS64:15361MB
 Value for row 0 assigned.     WS64: 3832MB  PMS64:15362MB
 Stop.                         WS64: 3844MB  PMS64:15325MB

当我在Process.WorkingSet64中看到任务管理器中的内存消耗是这样的。真实数字是多少?为什么在分配时分配内存?数组实际上是连续分配的内存吗?数组是数组吗?外星人存在吗? (戏剧性的背景音乐)

第 2 集: 我们做了一个小改动:

            //lArray[i * JMAX + j] = i * JMAX + j;
            var x= lArray[i * JMAX + j];

没有任何变化(在输出中)。存在与不存在的区别在哪里? (更戏剧性的背景音乐)现在我们正在等待一位神秘人物的回答(他们有一些数字和一个小的 'k' 在他们的名字下)。

第 3 集: 另一个变化:

    //lArray[IMAX * JMAX - 1] = 5000;
    //l1Array[IMAX * JMAX - 1] = 5000;
    //l2Array[IMAX * JMAX - 1] = 5000;
    //l3Array[IMAX * JMAX - 1] = 5000;
    //ShowTextWithMemAlloc("Last elements accessed.");
    long newIMAX = IMAX-3;
    long newJMAX = JMAX / 10;
    for (var i=0; i<newIMAX; i++)
    {
        for (var j=0; j<newJMAX; j++)
        {
            lArray[i * newJMAX + j] = i * newJMAX + j;
            //var x= lArray[i * JMAX + j];
        }
        //ShowTextWithMemAlloc($"Value for row {i} assigned.");
    }
    ShowTextWithMemAlloc($"{newIMAX*newJMAX} values assigned.");

输出:

 Start.                             WS64:   14MB  PMS64:    8MB
 Arrays created.                    WS64:   15MB  PMS64:15369MB
 20000000 values assigned.          WS64:  168MB  PMS64:15369MB
 Stop.                              WS64:  168MB  PMS64:15369MB

一个阵列的 PMS64 (15369-8)/4 = 3840MB 这不是稀疏数组,而是部分填充的数组;)。我正在使用这 168MB 的完整空间。

回答一些问题"Why do you not use the exact size?"。因为我不知道?数据可以来自多个用户定义的 SQL。 "Why do you not resize it?"。调整大小创建一个新数组并复制值。现在是复制、内存的时候了,最后邪恶的 GC 来吃掉你。

我是不是浪费内存了。 (我不记得了。外星人?!) 如果是,多少钱? 0、(3840-168)MB 或 (15369-8-168)MB?

结语:

评论是评论还是回答?

is contiguous memory actually contiguous memory?

答案给出答案了吗?神秘。 (more music)

(Scully:Mulder,蟾蜍刚刚从天上掉下来! Mulder:我猜他们的降落伞没有打开。)

谢谢大家!

工作集不是分配的内存量。它是进程当前可用的页面集。 Windows 围绕这个实施了各种政策,这个数字通常很难解释。

这里,内存可能被请求为从 OS 归零。对页面的第一次访问实际上使清零页面可用。

您应该查看私有字节。

您不能稀疏地分配 .NET 数组。也许,您应该考虑使用一些提供稀疏数组印象的数据结构。

Is an array actually a continuous allocated memory?

是的,从 CLR 和 .NET 代码的角度来看 运行ning。 OS 可能会耍花招,例如在第一次读取或写入时在页面中出现延迟错误。

对于"Episode 2",答案是读取和写入都会发生故障。我不太了解第 3 集的内容,但我认为它涉及的页面更少。

Did I waste memory

这个说起来比较复杂。只要页面未被触及,它们实际上就不会被使用。例如,它们可用于文件缓存或其他程序常驻工作集。不过,它们确实计入系统的提交费用。 Windows 向您保证它可以为您提供这些页面。您不会 运行 在某些随机内存访问时内存不足。 Linux 不保证。它有 OOM 杀手作为缓解措施。

在极端情况下,如果您像这样分配 1TB,则 RAM 和页面文件大小的总和也需要超过 1TB,即使 space 中的 none 最终可能会被使用.

考虑使用内存映射文件。在这里,文件是后备存储,RAM 被视为缓存。这将以完全相同的方式运行。