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

Posted

技术标签:

【中文标题】减少 .NET 应用程序的内存使用?【英文标题】:Reducing memory usage of .NET applications? 【发布时间】:2010-11-23 12:33:35 【问题描述】:

有哪些技巧可以考虑以下简单的 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);

x86 Release 在 Visual Studio 之外的结果:

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

这稍微好一点,但对于这么简单的程序来说似乎还是过分了。有什么技巧可以让 C# 进程更精简一些吗?我正在编写一个大多数时间都在后台运行的程序。我已经在单独的Application Domain 中处理了任何用户界面内容,这意味着可以安全地卸载用户界面内容,但仅在后台占用 10 MB 似乎过多。

P.S. 至于我为什么会关心 ---(超级)用户往往会担心这些事情。即使它对性能几乎没有影响,半精通技术的用户(我的目标受众)也倾向于对后台应用程序的内存使用大发雷霆。甚至当我看到 Adob​​e Updater 占用 11 MB 内存并被 Foobar2000 的平静触感所抚慰时我也感到震惊,即使在播放时也可能占用不到 6 MB。我知道在现代操作系统中,这些东西在技术上真的没有那么重要,但这并不意味着它对感知没有影响。

【问题讨论】:

你为什么要关心?私人工作集相当低。如果不需要内存,现代操作系统将分页到磁盘。现在是 2009 年。除非你在嵌入式系统上构建东西,否则你不应该关心 10MB。 停止使用 .NET,您可以拥有小程序。要加载 .NET 框架,需要将大量大 DLL 加载到内存中。 内存价格呈指数下降(是的,您现在可以订购具有 24 GB RAM 的戴尔家用计算机系统)。除非您的应用程序使用 >500MB 的优化是不必要的。 @LeakyCode 我真的很讨厌现代程序员这样想,你应该关心你的应用程序的内存使用情况。我可以说大多数现代应用程序,主要是用 java 或 c# 编写的,在资源管理方面非常低效,多亏了 2014 年,我们可以在 win95 和 64mb 的 ram 上运行与 1998 年一样多的应用程序...现在只有 1 个浏览器实例消耗 2gb 的内存和大约 1gb 的简单 IDE。 Ram 很便宜,但这并不意味着你应该浪费它。 @Petr 你应该关心资源管理。程序员的时间也是一种资源。请不要过度概括浪费 10MB 到 2GB。 【参考方案1】:
    您可能想查看 Stack Overflow 问题.NET EXE memory footprint。 MSDN 博文Working set != actual memory footprint 旨在揭开工作集、进程内存以及如何准确计算 RAM 总消耗的神秘面纱。

我不会说您应该忽略应用程序的内存占用——显然,更小、更高效的做法往往是可取的。但是,您应该考虑您的实际需求。

如果您正在编写一个注定要在个人 PC 上运行的标准 Windows 窗体和 WPF 客户端应用程序,并且很可能是用户操作的主要应用程序,那么您可以避免在内存分配方面更加懒散. (只要它全部被释放。)

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

【讨论】:

【参考方案2】:

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

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

【讨论】:

【参考方案3】:

还有一些方法可以减少这个简单程序的私有工作集:

    NGEN 您的应用程序。这会从您的流程中消除 JIT 编译成本。

    使用 MPGO reducing memory usage 训练您的应用程序,然后使用 NGEN 训练它。

【讨论】:

【参考方案4】:

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

在.NET 中你必须忍受的一件事是你的 IL 代码的本机映像的大小是巨大的

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

人们也倾向于编写阻塞内存的代码,远远超过必要的时间。

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

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

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

【讨论】:

【参考方案5】:

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

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

如果您想保持较小的占用空间,您将不得不考虑内存使用情况。这里有几个想法:

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

无论如何,这可能不是一个详尽的列表,而只是一些想法。

【讨论】:

IOW,用于减少本机代码大小的技术在 .NET 中是否有效? 我猜有一些重叠,但是在使用本机代码时,您在内存使用方面有更多选择。【参考方案6】:

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

如果您使用返回大量数据的 COM 组件 (比如大的 2xN 双精度数组)并且只有一小部分 需要,然后可以编写一个包装 COM 组件 对 .NET 隐藏内存并仅返回 需要。

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

【讨论】:

【参考方案7】:

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

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

【讨论】:

【参考方案8】:

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

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

【讨论】:

【参考方案9】:

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

从操作方法:

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

【讨论】:

以上是关于减少 .NET 应用程序的内存使用?的主要内容,如果未能解决你的问题,请参考以下文章

如何在内存分析期间跟踪 .NET 应用程序中内存访问的频率和数量?

ASP.Net Worker 进程内存配置文件工具

减少“代码”内存使用量

如何减少 Cobalt 的内存使用量

内存使用率高 - 应用程序响应慢:已用内存值未减少 + 可用内存值未增加

UIPageControllerView 回收页面减少内存使用