减少.NET应用程序的内存使用量?

时间:2009-08-27 19:40:57

标签: c# .net optimization memory memory-optimization

有哪些提示可以减少.NET应用程序的内存使用量?考虑以下简单的C#程序。

class Program
{
    static void Main(string[] args)
    {
        Console.ReadLine();
    }
}

x64 发布模式下编译并在Visual Studio外部运行,任务管理器报告以下内容:

Working Set:          9364k
Private Working Set:  2500k
Commit Size:         17480k

如果仅针对 x86 编译它会好一点:

Working Set:          5888k
Private Working Set:  1280k
Commit Size:          7012k

然后我尝试了以下程序,它执行相同的操作但在运行时初始化后尝试修剪进程大小:

class Program
{
    static void Main(string[] args)
    {
        minimizeMemory();
        Console.ReadLine();
    }

    private static void minimizeMemory()
    {
        GC.Collect(GC.MaxGeneration);
        GC.WaitForPendingFinalizers();
        SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle,
            (UIntPtr) 0xFFFFFFFF, (UIntPtr)0xFFFFFFFF);
    }

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetProcessWorkingSetSize(IntPtr process,
        UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize);
}

Visual Studio外部 x86 发布的结果:

Working Set:          2300k
Private Working Set:   964k
Commit Size:          8408k

哪个好一点,但对于这样一个简单的程序来说似乎仍然过分。是否有任何技巧可以使C#流程更精简?我正在编写一个旨在大多数时间在后台运行的程序。我已经在单独的Application Domain中进行任何用户界面操作,这意味着可以安全地卸载用户界面内容,但是当它只是坐在后台时占用10 MB似乎过多。

P.S。至于为什么我会关心---(Power)用户往往会担心这些事情。即使它对性能几乎没有影响,半精通技术的用户(我的目标受众)倾向于对背景应用程序内存使用情况进行调整。当我看到Adobe Updater占用11 MB的内存并且感受到Foobar2000的平静触摸时,即使我玩的时候也可以拿到6 MB以下,我甚至会感到蠢蠢欲动。我知道在现代操作系统中,这些东西在技术上并不重要,但这并不意味着它对感知没有影响。

9 个答案:

答案 0 :(得分:44)

.NET应用程序与本机应用程序相比具有更大的占用空间,因为它们都必须在此过程中加载运行时和应用程序。如果你想要一些非常整洁的东西,.NET可能不是最好的选择。

但是,请记住,如果您的应用程序主要处于休眠状态,那么必要的内存页面将被换出内存,因此在大多数情况下并不会对系统造成太大的负担。

如果要保持足迹较小,则必须考虑内存使用情况。以下是一些想法:

  • 减少对象的数量,并确保不要持有超过要求的任何实例。
  • 请注意List<T>以及在需要时将容量增加一倍的类似类型,因为它们可能导致50%的浪费。
  • 您可以考虑在引用类型上使用值类型来强制堆栈上有更多内存,但请记住,默认堆栈空间只有1 MB。
  • 避免使用超过85000字节的对象,因为它们会转到未压缩的LOH,因此可能很容易碎片化。

这可能不是一个详尽无遗的清单,只是一些想法。

答案 1 :(得分:32)

  1. 您可能需要查看Stack Overflow问题 .NET EXE memory footprint
  2. MSDN博客文章 Working set != actual memory footprint 就是揭开工作集,进程内存的神秘面纱,以及如何对内存消耗总量进行准确计算。
  3. 我不会说你应该忽略你的应用程序的内存占用 - 显然,更小,更高效往往是可取的。但是,您应该考虑您的实际需求。

    如果您正在编写一个标准的Windows窗体和WPF客户端应用程序,这些应用程序注定要在个人的PC上运行,并且很可能是用户操作的主要应用程序,那么您可以更加缺乏内存分配的缺点。 。 (只要这一切都被解除分配。)

    但是,要解决一些不要担心的人:如果您正在编写将在终端服务环境中运行的Windows窗体应用程序,则可能在10,20或更多用户可能使用的共享服务器上,然后是的,你绝对必须考虑内存使用情况。你需要保持警惕。解决这个问题的最佳方法是使用良好的数据结构设计,并遵循有关何时以及分配的最佳实践。

答案 2 :(得分:16)

在这种情况下,您需要考虑的一件事是CLR的内存成本。每个.Net进程都会加载CLR,因此会考虑内存因素。对于这样一个简单/小程序,CLR的成本将主导您的内存占用。

构建一个真实的应用程序并查看与该基准程序的成本相比的成本会更有启发性。

答案 3 :(得分:7)

本身没有具体建议,但您可以查看CLR Profiler(从Microsoft免费下载)。
安装完成后,请查看此how-to page

从方法:

  

本方法向您展示如何使用   CLR Profiler工具来调查你的   应用程序的内存分配   轮廓。您可以使用CLR Profiler   识别导致记忆的代码   问题,如内存泄漏和   垃圾过多或效率低下   集合。

答案 4 :(得分:6)

可能希望查看“真实”应用程序的内存使用情况。

与Java类似,无论程序大小如何,运行时都会有一些固定的开销,但在此之后内存消耗会更加合理。

答案 5 :(得分:4)

仍然有办法减少这个简单程序的私人工作集:

  1. NGEN您的申请。这将从您的流程中删除JIT编译成本。

  2. 使用MPGO reducing memory usage训练您的申请,然后使用NGEN。

答案 6 :(得分:2)

有很多方法可以减少您的足迹。

您在.NET中始终需要考虑的一件事是,您的IL代码的原生图片大小巨大

此代码无法在应用程序实例之间完全共享。即使NGEN'ed程序集不是完全静态的,它们仍然有一些需要JITting的小部件。

人们也倾向于编写能够阻止内存远远超过必要的代码。

一个经常看到的例子:使用Datareader,将内容加载到DataTable中,只是将其写入XML文件。您可以轻松地遇到OutOfMemoryException。 OTOH,您可以使用XmlTextWriter并滚动Datareader,在滚动数据库游标时发出XmlNodes。 这样,您只在内存中拥有当前数据库记录及其XML输出。这将永远(或不太可能)获得更高的垃圾收集生成,因此可以重复使用。

同样适用于获取某些实例的列表,执行某些操作(生成数千个新实例,可能会在某处继续引用),即使之后您不需要它们,您仍然会引用所有内容,直到之后foreach。 明确地使输入列表和临时副产品无效,即使在退出循环之前,也可以重复使用此内存。

C#具有一个称为迭代器的出色功能。它们允许您通过滚动输入来流式传输对象,并且只保留当前实例,直到您获得下一个实例。即使使用LINQ,您仍然不需要保留所有内容,因为您希望对其进行过滤。

答案 7 :(得分:1)

解决标题中的一般问题,而不是 具体问题:

如果您使用的是返回大量数据的COM组件 (比如大的2xN双打阵列)并且只有一小部分 然后需要编写一个包装器COM组件 从.NET隐藏内存并仅返回数据 需要。

这就是我在主应用程序中所做的事情 显着改善了内存消耗。

答案 8 :(得分:0)

我发现使用SetProcessWorkingSetSize或EmptyWorkingSet API在长时间运行的进程中定期强制将内存页面强制到磁盘,可能会导致计算机上所有可用的物理内存在计算机重新启动之前有效消失。我们将.NET DLL加载到本机进程中,该进程在执行内存密集型任务后使用EmptyWorkingSet API(使用SetProcessWorkingSetSize的替代方法)来减少工作集。我发现在1天到一周之间的任何地方,一台机器在任务管理器中显示99%的物理内存使用量,而没有任何进程显示使用任何重要的内存使用量。机器无响应后不久需要重启。所述机器是在物理和虚拟硬件上运行的二十多个Windows Server 2008 R2和2012 R2服务器。

将.NET代码加载到本机进程中可能与它有关,但使用EmptyWorkingSet(或SetProcessWorkingSetSize)需要您自担风险。也许只在首次启动应用程序后使用它一次。我决定禁用代码,让Garbage Collector自行管理内存使用情况。