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


108

有什么技巧可以减少.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#流程更精简吗?我正在编写一个旨在大多数时候在后台运行的程序。我已经在单独的“ 应用程序域”中进行了任何用户界面操作,这意味着可以安全地卸载用户界面操作,但是仅在后台使用时要占用10 MB的空间。

PS关于我为什么要关心---(高级)用户倾向于担心这些事情。即使对性能几乎没有影响,精通半技术的用户(我的目标受众)也倾向于对后台应用程序的内存使用情况感到不满。甚至当我看到Adobe Updater占用11 MB的内存并因Foobar2000的平稳触摸而感到安慰时,即使我在播放时,它也占用不到6 MB的内存,我都感到非常惊讶。我知道在现代操作系统中,这些东西在技术上确实无关紧要,但这并不意味着它对感知没有影响。


14
你为什么要在乎?私有工作集很低。如果不需要内存,现代操作系统将分页到磁盘。那是2009年。除非您要在嵌入式系统上构建东西,否则您不必在意10MB。
Mehrdad Afshari,2009年

8
停止使用.NET,您将拥有小型程序。若要加载.NET框架,需要将许多大DLL加载到内存中。
亚当·希尔斯

2
内存价格成倍下降(是的,您现在可以订购具有24 GB RAM的Dell家用计算机系统)。除非您的应用程序使用> 500MB,否则没有必要进行优化。
亚历克斯

28
@LeakyCode我真的很讨厌现代程序员这样思考,您应该关心应用程序的内存使用情况。我可以说,大多数现代应用程序(主要用Java或C#编写)在资源管理方面都非常无效,这要归功于2014年,我们可以在1998年在win95和64mb ram上运行尽可能多的应用程序...现在只有1个浏览器实例吃了2gb的ram和大约1gb的简单IDE。Ram很便宜,但这并不意味着您应该浪费它。
彼得

6
@Petr您应该关心资源管理。程序员时间也是资源。请不要过于笼统地将10MB浪费到2GB。
Mehrdad Afshari 2014年

Answers:


33
  1. 您可能需要检查堆栈溢出问题.NET EXE内存占用量
  2. MSDN博客文章“ 工作集!=实际内存占用量”全部是关于使工作集,进程内存以及如何对RAM中的总消耗量进行精确计算的神秘化。

我不会说您应该忽略应用程序的内存占用-显然,更小,更高效的确是可取的。但是,您应该考虑自己的实际需求。

如果编写的标准Windows Forms和WPF客户端应用程序注定要在个人PC上运行,并且很可能是用户在其中运行的主要应用程序,那么您可以摆脱对内存分配的不屑一顾。(只要将其全部释放即可。)

但是,要解决这里的一些人说不用担心的问题:如果要编写将在终端服务环境中运行的Windows Forms应用程序,并且该共享服务器可能会由10、20或更多用户使用,则是,您绝对必须考虑内存使用情况。您将需要保持警惕。解决此问题的最佳方法是采用良好的数据结构设计,并遵循有关分配时间和分配内容的最佳做法。


45

与本地应用程序相比,.NET应用程序将具有更大的占用空间,因为它们都必须在运行时中加载运行时和应用程序。如果您想要真正整洁的东西,.NET可能不是最佳选择。

但是,请记住,如果您的应用程序大部分时间处于睡眠状态,则必需的内存页面将被换出内存,因此在大多数情况下,实际上并不会给系统造成太大的负担。

如果您想减小占用空间,则必须考虑内存使用情况。这里有一些想法:

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

无论如何,这可能不是一个详尽的清单,而只是几个想法。


IOW,用于减少本机代码大小的相同技术在.NET中也有效?
罗伯特·弗雷泽

我想这里有些重叠,但是使用本地代码时,在使用内存方面您有更多选择。
布赖恩·拉斯穆森

17

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

与该基准程序的成本相比,构建一个实际的应用程序并查看其成本将更具启发性。


7

本身没有具体建议,但您可以看看CLR Profiler(可从Microsoft免费下载)。
安装后,请查看此操作方法页面

从操作方法:

本操作方法向您展示如何使用CLR Profiler工具调查应用程序的内存分配配置文件。您可以使用CLR Profiler识别导致内存问题的代码,例如内存泄漏以及过多或效率低下的垃圾回收。



4

仍然有一些方法可以减少此简单程序的私有工作集:

  1. NGEN您的应用程序。这从您的过程中消除了JIT编译成本。

  2. 使用MPGO训练您的应用程序以减少内存使用量,然后使用 NGEN进行训练。


2

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

.NET中,您必须始终忍受的一件事是,IL代码的本机映像的大小很大

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

人们还倾向于编写阻塞内存的代码,其时间远远超出了必要的时间。

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

这同样适用于获取一些实例的列表,执行某些操作(产生数以千计的新实例,这些实例可能仍在某处被引用),即使您以后不需要它们,您仍要引用所有内容,直到foreach之后。显式清空输入列表和临时副产品意味着,即使在退出循环之前,也可以重用此内存。

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


1

在标题中回答一般性问题,而不是具体问题:

如果您使用的COM组件返回大量数据(例如,较大的2xN双精度数组),而只需要一小部分,则可以编写一个包装COM组件,该组件可从.NET中隐藏内存并仅返回数据。需要。

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


0

我发现在长时间运行的过程中使用SetProcessWorkingSetSize或EmptyWorkingSet API定期将内存页面强制到磁盘上会导致计算机上的所有可用物理内存有效地消失,直到重新启动计算机为止。我们将.NET DLL加载到本机进程中,该进程将使用EmptyWorkingSet API(使用SetProcessWorkingSetSize的替代方法)在执行占用大量内存的任务后减少工作集。我发现,在一天到一周的任何时间里,一台计算机在“任务管理器”中将显示99%的物理内存使用率,而没有进程显示正在使用任何显着的内存使用率。不久之后,计算机将变得无响应,需要重新启动计算机。这些机器是超过2台同时在物理和虚拟硬件上运行的Windows Server 2008 R2和2012 R2服务器。

将.NET代码加载到本机进程中可能与它有关,但是使用EmptyWorkingSet(或SetProcessWorkingSetSize)的风险自负。最初启动应用程序后,也许只使用一次。我已决定禁用该代码,并让垃圾收集器自行管理内存使用情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.