111

有哪些技巧可以减少 .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 Release上的结果:

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

哪个好一点,但对于这样一个简单的程序来说,它仍然显得过分。有什么技巧可以让 C# 进程更精简一些吗?我正在编写一个大多数时间都在后台运行的程序。我已经在一个单独的应用程序域中做任何用户界面的东西,这意味着用户界面的东西可以安全地卸载,但是当它只是坐在后台时占用 10 MB 似乎过多。

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

4

9 回答 9

46

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

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

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

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

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

于 2009-08-27T19:44:37.303 回答
34
  1. 您可能想查看 Stack Overflow 问题.NET EXE 内存占用
  2. MSDN 博客文章Working set != actual memory 占用是关于揭开工作集、进程内存以及如何准确计算 RAM 总消耗的神秘面纱。

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

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

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

于 2009-08-27T20:00:30.440 回答
17

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

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

于 2009-08-27T19:45:01.390 回答
8

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

从操作方法:

本 How To 向您展示了如何使用 CLR Profiler 工具来调查应用程序的内存分配配置文件。您可以使用 CLR Profiler 来识别导致内存问题的代码,例如内存泄漏和过多或低效的垃圾收集。

于 2009-08-27T19:43:53.653 回答
7

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

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

于 2009-08-27T19:46:15.193 回答
5

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

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

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

于 2012-07-20T20:02:51.377 回答
3

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

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

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

人们还倾向于编写比必要时间更长的阻塞内存的代码。

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

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

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

于 2009-10-28T09:32:35.867 回答
1

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

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

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

于 2009-08-27T20:47:14.790 回答
0

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

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

于 2015-02-18T09:34:18.960 回答