Уменьшение использования памяти приложений .NET?

Каковы некоторые советы по сокращению использования памяти приложениями .NET? Рассмотрим следующую простую программу С#.

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 Отпустите за пределами Visual Studio:

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

Это немного лучше, но для такой простой программы все еще кажется чрезмерным. Есть ли какие-либо трюки, чтобы сделать процесс С# немного более компактным? Я пишу программу, предназначенную для работы в фоновом режиме большую часть времени. Я уже делаю какие-либо материалы пользовательского интерфейса в отдельном Application Domain, что означает, что материал пользовательского интерфейса можно безопасно выгружать, но он занимает 10 Мб когда он просто сидит в фоновом режиме, кажется чрезмерным.

P.S. Что касается меня, я бы хотел, чтобы пользователи (Power) часто беспокоились об этих вещах. Даже если это практически не влияет на производительность, пользователи с полутехнологиями (моя целевая аудитория), как правило, вступают в свои подходы к использованию памяти фонового приложения. Даже я урод, когда вижу, что Adobe Updater принимает 11 Мб памяти и чувствует себя успокоенным успокаивающим прикосновением Foobar2000, которое может занять до 6 Мб даже при игре. Я знаю в современных операционных системах, этот материал действительно не имеет большого значения технически, но это не значит, что он не влияет на восприятие.

Ответ 1

  • Возможно, вы захотите проверить вопрос о переполнении стека .NET EXE.
  • Сообщение в блоге MSDN Рабочий набор!= фактический объем памяти - все о демистификации рабочего набора, памяти процесса и способах точных вычислений на общее потребление в ОЗУ.

Я не буду говорить, что вы должны игнорировать объем памяти вашего приложения - очевидно, меньший и более эффективный, как правило, желателен. Однако вы должны учитывать, каковы ваши фактические потребности.

Если вы пишете стандартные клиентские приложения Windows Forms и WPF, которые предназначены для работы на отдельном ПК и, вероятно, будут основным приложением, в котором работает пользователь, вы можете избавиться от того, что вам больше не хватает памяти, (Пока все освобождается.)

Однако, чтобы обратиться к некоторым людям, которые говорят, что не стоит беспокоиться об этом: если вы пишете приложение Windows Forms, которое будет работать в среде служб терминалов, на общем сервере, возможно, используемом 10, 20 или более пользователями, то да, вы абсолютно должны учитывать использование памяти. И вам нужно быть бдительными. Лучший способ справиться с этим - хорошая структура структуры данных и использование лучших практик в отношении того, когда и что вы выделяете.

Ответ 2

Приложения .NET будут иметь больший размер по сравнению с собственными приложениями из-за того, что они оба должны загружать среду выполнения и приложение в процесс. Если вы хотите что-то действительно опрятное,.NET, возможно, не лучший вариант.

Однако имейте в виду, что если приложение в основном спадает, необходимые страницы памяти будут заменены на память и, следовательно, на самом деле это не большая часть нагрузки на систему в большинстве случаев.

Если вы хотите ограничить площадь, вам придется подумать об использовании памяти. Вот несколько идей:

  • Уменьшите количество объектов и не задерживайте их на любом экземпляре дольше, чем требуется.
  • Имейте в виду List<T> и аналогичные типы, которые при необходимости удваивают емкость, поскольку они могут привести к 50% отходов.
  • Вы можете использовать типы значений по ссылочным типам, чтобы наложить больше памяти на стек, но имейте в виду, что пространство стека по умолчанию составляет всего 1 МБ.
  • Избегайте объектов размером более 85000 байт, так как они перейдут на LOH, который не уплотняется и, следовательно, может быть легко фрагментирован.

Это, вероятно, не исчерпывающий список, а всего несколько идей.

Ответ 3

В этом случае необходимо учитывать стоимость памяти для CLR. CLR загружается для каждого процесса .Net и, следовательно, факторов в соображениях памяти. Для такой простой/небольшой программы стоимость CLR будет доминировать над вашей памятью.

Было бы более поучительно строить реальное приложение и рассматривать стоимость этого по сравнению со стоимостью этой базовой программы.

Ответ 4

Никаких конкретных предложений не существует, но вы можете взглянуть на CLR Profiler (бесплатная загрузка из Microsoft).

После того как вы его установили, ознакомьтесь с этой практической страницей.

Из инструкции:

В этом How To показано, как использовать Инструмент профилировщика CLR для исследования вашего распределение памяти приложения профиль. Вы можете использовать CLR Profiler для идентифицировать код, вызывающий память проблемы, такие как утечки памяти и чрезмерный или неэффективный мусор коллекция.

Ответ 5

Возможно, вам стоит взглянуть на использование памяти "реального" приложения.

Подобно Java, существует некоторая фиксированная сумма накладных расходов для среды выполнения независимо от размера программы, но потребление памяти будет намного более разумным после этой точки.

Ответ 6

Есть еще способы уменьшить частный рабочий набор этой простой программы:

  • NGEN ваше приложение. Это удаляет стоимость компиляции JIT из вашего процесса.

  • Обучайте свое приложение с помощью MPGO уменьшая использование памяти, а затем NGEN.

Ответ 7

Существует множество способов уменьшить ваш след.

Одна вещь, с которой вам всегда придется жить в .NET, заключается в том, что размер собственного изображения вашего IL-кода огромный

И этот код не может быть полностью разделен между экземплярами приложения. Даже сборки NGEN'ed не являются полностью статичными, у них есть еще некоторые небольшие части, которые нуждаются в JITting.

Люди также склонны писать код, который блокирует память намного дольше, чем необходимо.

Часто просматриваемый пример: взятие Datareader, загрузка содержимого в DataTable, чтобы записать его в файл XML. Вы можете легко запустить OutOfMemoryException. OTOH, вы можете использовать XmlTextWriter и прокручивать Datareader, испуская XmlNodes при прокрутке курсора базы данных. Таким образом, у вас есть только текущая запись базы данных и ее вывод XML в памяти. Который никогда (или вряд ли) получит более высокую сборку мусора и, следовательно, может быть повторно использован.

То же самое относится к получению списка некоторых экземпляров, выполняя некоторые вещи (которые порождают тысячи новых экземпляров, которые могут оставаться где-то на месте), и даже если после этого они вам не понадобятся, вы все равно ссылаетесь на все до тех пор, пока foreach. Явно исключая ваш список входных данных и ваши временные побочные продукты, эта память может быть повторно использована даже до выхода из цикла.

С# имеет отличную функцию, называемую итераторами. Они позволяют вам передавать объекты, прокручивая ваш вход и сохраняя текущий экземпляр, пока не получите следующий. Даже используя LINQ, вам все равно не нужно поддерживать все это только потому, что вы хотите, чтобы он был отфильтрован.

Ответ 8

Решение общего вопроса в заголовке, а не конкретный вопрос:

Если вы используете COM-компонент, который возвращает много данных (скажем, большие 2xN массивы двойников) и только малая доля необходимо, чтобы можно было написать COM-компонент оболочки, который скрывает память от .NET и возвращает только данные, которые необходимо.

Это то, что я сделал в своем основном приложении, и это значительно улучшило потребление памяти.

Ответ 9

Я обнаружил, что использование API SetProcessWorkingSetSize или EmptyWorkingSet для периодического перемещения страниц памяти на диск в длительном процессе может привести к тому, что вся доступная физическая память на машине будет эффективно исчезать до перезагрузки компьютера. У нас была DLL-библиотека .NET, загруженная в собственный процесс, который использовал бы API EmptyWorkingSet (альтернативу использованию SetProcessWorkingSetSize), чтобы уменьшить рабочий набор после выполнения задачи с интенсивной памятью. Я обнаружил, что через какое-то время от 1 дня до недели на компьютере будет отображаться 99% использования физической памяти в диспетчере задач, в то время как никакие процессы не показывали использование значительного использования памяти. Вскоре после того, как машина перестанет реагировать, потребуется жесткая перезагрузка. На указанных машинах было более двух десятков серверов Windows Server 2008 R2 и 2012 R2, работающих как на физическом, так и на виртуальном оборудовании.

Возможно, что код .NET, загруженный в собственный процесс, имел к этому какое-то отношение, но на свой страх и риск использует EmptyWorkingSet (или SetProcessWorkingSetSize). Возможно, используйте его только один раз после первого запуска вашего приложения. Я решил отключить код и позволить сборщику мусора самостоятельно использовать память.