【问题标题】:.NET stack memory limit.NET 堆栈内存限制
【发布时间】:2012-04-02 17:10:18
【问题描述】:

我正在使用 C#、.NET 4.0、64 位。我需要在内存中存储 5 亿个用于计算的“数据点”。我需要决定是将它们创建为结构对象还是类对象。结构看起来要快得多。

堆栈有内存限制吗?如果可以,如何调整。

在堆栈上存储这么多数据会影响系统的整体性能吗?

(顺便说一句,我知道 .NET 中的单个对象大小限制,因此正在解决这个问题 - 数据将存储在多个集合中)。

【问题讨论】:

  • 你确定你没有把堆栈和堆混淆吗?
  • 您基于什么声称/假设结构比类“快得多”?
  • 我认为 OP 在堆栈分配和结构之间建立了严格的关系,这是错误的。这些是完全不同的主题,可能是相关的。

标签: .net memory 64-bit stack limit


【解决方案1】:

你问错问题了。如果堆栈大小很重要,那么您做错了什么。

如果您使用许多数据点,您会将它们放在一个集合中,例如数组。数组总是在堆上分配。结构数组嵌入各个结构并形成连续的内存块。 (如果超过 2GB,则需要多个数组)。

而对于引用类型,数组将只包含引用,并且对象在堆上单独分配。一个堆分配大约有 16 个字节的开销,数组中的引用占了另外 8 个。
由于间接性,您还会获得更差的缓存局部性,并且 GC 必须做更多的工作来抓取所有这些引用。

我的结论是,如果你有很多小数据点,把它们做成一个结构,然后把它们放在一个数组中。

【讨论】:

    【解决方案2】:

    您要将数据存储在数组中,而数组始终存储在堆中。所以无论你是使用结构还是类来保存这些数组都没有关系。您可能希望确保您的数据点是值类型(即结构),以便可以在连续的内存块中有效地分配数据点数组。

    堆和堆栈分配内存之间的性能差异最有可能在短时间内分配和释放的小对象中看到。对于您描述的大小的长寿命对象,我希望堆栈和堆分配的内存之间的性能没有差异。

    【讨论】:

    • 问题是,如果结构是在堆上分配的,它就会失去快速分配的“好处”(从问题的角度来看)。
    • @Tigran 一旦您对 5 亿个数据点执行操作,这些差异将无法衡量。
    • @Tigran 如果它是数组的一部分,则不是。结构将在数组中的一个连续内存块中结束,而类将获得一个新实例(具有相关的 16 字节开销)和额外的间接。
    • @CodeInChaos: 所以分配在堆上或栈上的结构具有相同的性能,只有当它成为数组的一部分??
    • @Tigran 值类型嵌入到包含结构中。不管那是堆栈、类还是数组。引用类型仅将指针嵌入到包含结构中。您应该阅读一些关于什么是值类型的内容。 Eric Lippert 在他的博客和 SO 上都写了很多关于此的文章。
    【解决方案3】:

    可以为您的数据点使用类。在这种情况下,内存将分配在堆上。

    但考虑到您说的是 5 亿个数据点,特别是因为您在 .NET 世界中编程,应用程序的内存限制更受限制,我强烈建议您使用某种嵌入式数据库,例如 sqlite,例子。通过这种方式,您可以避免将所有数据点同时保存在内存中,而只将那些您需要用于计算的数据点现在

    【讨论】:

    • 根据我的经验,需要快速访问大量数据而无需数据库开销是很常见的。如果将它们全部加载到内存中,仅使用引用类型就会产生 12GB 的开销。不好。
    • @CodeInChaos:同意。事实上,不要按照建议将它们全部加载到内存中,而是使用一些db 层来保存数据。
    • 出于性能原因,您通常希望将它们全部保存在内存中。例如,我经常使用物理模拟,其中数十 GB 的数据保存在大型数组中。
    • @CodeInChaos:5 亿个对象?在我遇到这种规模的工作负载的经验中,通常不满足实时性能要求。假设 99% 的情况可能是这样。老实说,不要认为 1% 是 OP 的问题 :) +1
    【解决方案4】:

    令人惊讶的是,似乎没有人试图回答实际问题。

    我完全理解这是 99.9% 的时候问的错误问题,但知道结果仍然很有趣(至少我很好奇)。

    测试程序

    使用不安全代码和stackalloc 关键字真的很简单。

    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 100; i < Int32.MaxValue; i+=10)
            {
                StackCheck(i);
                Console.WriteLine($"Successfully allocated {i} bytes on the stack");
            }
        }
    
        public static unsafe void StackCheck(int size)
        {
            byte* array = stackalloc byte[size];
        }
    }
    

    结果

    请注意,这是 100% 的实现细节,可能因 CLR、CLR 版本、操作系统或个别机器而异。在我的实验中,完整的 .NET Framework 4.7.2 和 .NET Core 2.1.4 都崩溃了略高于 1MB 标记。有趣的是,它在运行之间甚至不一致,结果波动几百字节。

    调整堆栈限制

    您无法在现有线程上更改堆栈大小,但您可以在新线程上设置它

    Thread testThread = new Thread(() =>
    {
        for (int i = 1000; i < Int32.MaxValue; i+=1000)
        {
            StackCheck(i);
            Console.WriteLine($"Successfully allocated {i} bytes on the stack");
        }
    }, 200_000_000);
    testThread.Start();
    testThread.Join();
    

    很明显整个栈都是在创建线程的时候分配的,如果设置太大,Thread构造函数会抛出OutOfMemoryException

    但同样,这个测试主要是为了满足我自己的好奇心,正如其他人所说,不要这样做除非你真的知道自己在做什么

    【讨论】:

      猜你喜欢
      • 2011-02-16
      • 2012-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-20
      • 2011-08-15
      • 2010-10-28
      • 2017-02-08
      相关资源
      最近更新 更多