大数组如何分配内存?
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 被视为缓存。这将以完全相同的方式运行。
我正在寻找一种将大型 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 被视为缓存。这将以完全相同的方式运行。