【问题标题】:Allocating memory in C# is not actually allocating memory在 C# 中分配内存实际上并不是分配内存
【发布时间】:2019-01-17 08:37:36
【问题描述】:

当我声明一个新的字节数组时,内存使用量并没有增加。

byte[] test = new[1024*100];

只有在我遍历每个字节后它才真正开始占用内存。

我怎样才能让它实际提交内存而不必先使用它?

我将此用作带有 try catch 块的测试,以查看它是否无法分配内存,从而告诉我我的应用程序内存不足,不应尝试分配任何新对象。

编辑:由于我将要分配的对象与垃圾收集器和堆栈一起使用,我不想使用 malloc 进行分配,因为它可能在那里成功,但在尝试分配新的托管对象时失败。

【问题讨论】:

  • @maccettura 我不想要非托管内存
  • 使用 Process Exporer 等特殊查看器,您会看到它确实分配了内存。它不会显示在提交中,但是一旦您迭代检查提交的内容就会上升。您不能为 1 个对象 IIRC 分配超过 2 GB 的空间
  • @John 你能不能试着改变最后一个元素的值来确保数组内存被分配??
  • @John 您实际上试图解决的问题是什么? (您的问题是基于一个错误的观察:内存实际上是在您创建数组时提交的,但这不是重点。)
  • @John 只是根据需要分配数组 - 如果内存不足,它将失败。数组被分配在一个连续的块中,因此请注意,您可能在几个较小的块中拥有足够的内存,但在单个大块中却没有。不过,您的问题听起来仍然像 X-Y 问题 - 如果您确定自己非常需要这么多内存,您应该保留它并使用您自己的内存管理器。

标签: c# arrays memory garbage-collection ram


【解决方案1】:

如果您迁移到 .NET 6,您可以利用新引入的 NativeMemory 方法使用 C API 分配本机内存:

using System.Runtime.InteropServices;

unsafe
{
    byte* buffer = (byte*)NativeMemory.Alloc(200);

    NativeMemory.Free(buffer);
}

【讨论】:

    【解决方案2】:

    好吧,如果您真的想要映射所有内存,那么重复数组并用零填充它。无论如何,这是线性时间。为此使用Array.Clear。 C# 确实不是为这种内存优化而设计的。您应该关心的唯一资源是未损坏的资源。在 C# 中考虑其余内存消耗的正确方法是不考虑。在搜索这个问题之前,我真的不知道 C# 如何在数组声明期间处理内存分配,这有点意思,我没有使用高级抽象语言来关心这些东西。仅当您的工作应用程序遇到内存瓶颈并且需要优化时才有意义。

    一个stackalloc operator,你可以随意使用它,但它是一个不安全的运算符。还有GC.GetTotalMemory(),它可以显示当前的内存使用情况(托管对象),Microsoft.VisualBasic.Devices.ComputerInfo.TotalPhysicalMemory,它会给你机器的总RAM(不要介意名称中的VB,它是一个.NET框架方法,只需添加对Microsoft.VisualBasic.Devices) 的引用,因此使用此方法可以实现了解或多或少的目标,即是否有足够的内存来分配数组。

    更新:

    从 C# 7.2 开始,现在允许使用 stackalloc 而无需输入带有 Span<T>ReadOnlySpan<T> 类型的 unsafe 上下文。但是,我无法确定是否需要 stackalloc 指令来实际从堆栈中分配,或者如果分配的缓冲区未使用,是否可以省略它,因此可能仍然需要实际遍历缓冲区来欺骗编译器认为需要分配。

    【讨论】:

    • "commit" 是错误的词,地址空间 isnew 之后提交,并且操作系统在分页文件中为分配保留了空间。 “映射”是更好的词。
    【解决方案3】:

    创建了一个函数,它将尝试实际分配我指定的内存(如果不可能,则抛出异常,而不是撒谎),如果成功,垃圾收集器将立即收集。

    bool IsMemoryAvailable(int amount)
    {
        int size = (int)Math.Sqrt(amount) / 2;
    
        try
        {
            using (Bitmap bmpTest = new Bitmap(size, size))
            {
    
            }
    
            GC.Collect();
    
            return true;
        }
        catch(Exception x)
        {
            return false;
        }
    }
    

    【讨论】:

    • 不推荐显式调用 gc.collect。 blogs.msdn.microsoft.com/abhinaba/2008/05/01/…
    • @bhmahler 绝对是必需的,否则它不会释放刚刚分配的内存,并且当其他分配新对象的合法调用将在整个应用程序中失败时。
    • 这不是真的。当你使用一个 using 对象时,你退出时被释放。这告诉垃圾收集器它已准备好删除,并且会在需要时这样做。对 .net 中的垃圾收集器进行更多研究
    • @bhmahler 自己测试我的原始问题代码,并在您确认自己有多么错误时告诉我。
    • Bitmap 默认分配会消耗多少字节,size * size * 4?检查this link,并在您确认自己的错误程度时告诉我。你忘了明确设置PixelFormat
    【解决方案4】:

    如果你想让它分配所有字节,你可以使用 Array.Clear,它会用 0 填充所有值

    Array.Clear(test, 0, test.Length);
    

    这个简单的控制台应用程序可让您在创建并使用 clear 填充时监控内存使用情况

    class Program
    {
        static void Main(string[] args)
        {
            //Memory usage 7.1 MB
            Console.WriteLine("Hit enter to allocate");
            Console.ReadKey();
            byte[] data = new byte[1024*1024]; //Memory still 7.1 MB
            Console.WriteLine("Hit Enter to Fill with zeros!");
            Console.ReadKey();
            Array.Clear(data, 0, data.Length); //Memory now 8.1 MB
            Console.WriteLine("1MB filled, hit enter to exit");
            Console.ReadKey();
        }
    }
    

    【讨论】:

    • 如果我填充了我分配的数组的所有字节,它需要超过 500 毫秒,当我需要每个函数调用少于 16 毫秒时,这会很慢。我尝试将最后一个元素设置为一个值,以查看它是否会分配物理页面以便测试可以工作,但它似乎不起作用。此外,由于垃圾收集器在我执行 GC.Collect() 时不获取字节数组,因此会导致测试失败,并且当我稍后在应用程序中分配位图时,它会引发内存不足异常。跨度>
    • @John 不可能,仅仅填充一个数组就需要 500 毫秒。你如何填充你的数组以及你是如何测量它的?
    • 调用 clear 用零填充数组并分配内存。你可以看到它发生在看着公羊。听起来您确实使用了错误的工具来完成这项工作。如果您需要这种显式内存管理,那么您应该自己管理它。另外,为什么你的程序需要一次分配这么多内存?资源不能释放,内存不能重用吗?
    • @ckuri 他在千兆字节范围内分配。示例中的 7 MB 很好,但 1 GB 非常慢。
    猜你喜欢
    • 2018-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-09
    • 2018-08-12
    • 2015-07-23
    相关资源
    最近更新 更多