Какие советы по сокращению использования памяти приложениями .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);
}
Результаты в выпуске x86 вне Visual Studio:
Working Set: 2300k
Private Working Set: 964k
Commit Size: 8408k
Что немного лучше, но все же кажется чрезмерным для такой простой программы. Есть ли какие-нибудь уловки, чтобы сделать процесс C # немного проще? Я пишу программу, которая большую часть времени предназначена для работы в фоновом режиме. Я уже занимаюсь любым пользовательским интерфейсом в отдельном домене приложения, что означает, что пользовательский интерфейс можно безопасно выгружать, но занимать 10 МБ, когда он просто находится в фоновом режиме, кажется чрезмерным.
PS Насчет того, почему меня это волнует --- (Опытные) пользователи обычно беспокоятся об этих вещах. Даже если это почти не влияет на производительность, полутехнические пользователи (моя целевая аудитория) имеют тенденцию впадать в истерику по поводу использования памяти фоновыми приложениями. Даже я схожу с ума, когда вижу, что Adobe Updater занимает 11 МБ памяти, и чувствую себя успокаивающим от успокаивающего прикосновения Foobar2000, который может занимать менее 6 МБ даже во время игры. Я знаю, что в современных операционных системах эти вещи действительно не имеют большого значения технически, но это не значит, что они не влияют на восприятие.
источник
Ответы:
Я не скажу, что вы должны игнорировать объем памяти, занимаемый вашим приложением - очевидно, что желательны меньшие и более эффективные. Однако вам следует подумать о ваших реальных потребностях.
Если вы пишете стандартные клиентские приложения Windows Forms и WPF, которые предназначены для работы на индивидуальном ПК и, вероятно, будут основным приложением, в котором работает пользователь, вам может сойти с рук более скудное отношение к распределению памяти. (Пока все это освобождается.)
Тем не менее, чтобы обратиться к некоторым людям, которые говорят, что не беспокойтесь об этом: если вы пишете приложение Windows Forms, которое будет работать в среде служб терминалов на общем сервере, который, возможно, будет использоваться 10, 20 или более пользователями, тогда да , вы обязательно должны учитывать использование памяти. И вам нужно будет быть бдительным. Лучший способ решить эту проблему - разработать хорошую структуру данных и следовать лучшим практикам в отношении того, когда и что вы выделяете.
источник
Приложения .NET будут занимать больше места по сравнению с собственными приложениями из-за того, что они оба должны загружать среду выполнения и приложение в процессе. Если вам нужно что-то действительно аккуратное, .NET может быть не лучшим вариантом.
Однако имейте в виду, что если ваше приложение в основном находится в спящем режиме, необходимые страницы памяти будут выгружены из памяти и, таким образом, большую часть времени на самом деле не будут такой большой нагрузкой на систему.
Если вы хотите, чтобы занимаемая площадь оставалась небольшой, вам придется подумать об использовании памяти. Вот пара идей:
List<T>
и подобные типы, которые удваивают производительность, когда это необходимо, поскольку они могут привести к 50% отходов.Это, вероятно, ни в коем случае не исчерпывающий список, а всего лишь пара идей.
источник
В этом случае вам нужно учитывать одну вещь - это стоимость памяти для среды CLR. CLR загружается для каждого процесса .Net и, следовательно, учитывает требования к памяти. Для такой простой / небольшой программы стоимость CLR будет преобладать над объемом памяти.
Было бы гораздо более поучительно создать реальное приложение и посмотреть его стоимость по сравнению со стоимостью этой базовой программы.
источник
Как таковых конкретных предложений нет, но вы можете взглянуть на Средство профилирования среды CLR (бесплатно загружаемое с сайта Microsoft).
После того, как вы установили его, взгляните на эту страницу с инструкциями .
Из инструкции:
источник
Возможно, вы захотите посмотреть на использование памяти «настоящим» приложением.
Как и в случае с Java, существует фиксированная сумма накладных расходов для среды выполнения независимо от размера программы, но после этого потребление памяти будет гораздо более разумным.
источник
Есть еще способы уменьшить частный рабочий набор этой простой программы:
NGEN ваше приложение. Это устраняет затраты на JIT-компиляцию из вашего процесса.
Обучите свое приложение, используя MPGO, уменьшив использование памяти, а затем NGEN.
источник
Есть много способов уменьшить ваш след.
Одна вещь, с которой вам всегда придется жить в .NET, - это то, что размер собственного образа вашего IL-кода огромен.
И этот код не может быть полностью разделен между экземплярами приложения. Даже сборки, созданные на основе NGEN , не являются полностью статичными, в них все еще есть небольшие детали, требующие JIT-обработки.
Люди также склонны писать код, который блокирует память намного дольше, чем необходимо.
Часто встречающийся пример: использование Datareader, загрузка содержимого в DataTable только для записи в XML-файл. Вы можете легко столкнуться с OutOfMemoryException. OTOH, вы можете использовать XmlTextWriter и прокручивать Datareader, генерируя XmlNodes при прокрутке курсора базы данных. Таким образом, у вас будет в памяти только текущая запись базы данных и ее вывод XML. Который никогда (или вряд ли) получит более высокое поколение сборки мусора и, следовательно, может быть повторно использован.
То же самое относится к получению списка некоторых экземпляров, выполнению некоторых действий (которые порождают тысячи новых экземпляров, которые могут оставаться где-то ссылками), и даже если они вам не понадобятся впоследствии, вы все равно ссылаетесь на все до тех пор, пока не будет выполнен foreach. Явное обнуление вашего входного списка и ваших временных побочных продуктов означает, что эту память можно повторно использовать даже до выхода из цикла.
В C # есть отличная функция, называемая итераторами. Они позволяют передавать объекты в потоковом режиме путем прокрутки ввода и сохранять только текущий экземпляр, пока вы не получите следующий. Даже при использовании LINQ вам все равно не нужно хранить все это просто потому, что вы хотите, чтобы это было отфильтровано.
источник
Отвечая на общий вопрос в заголовке, а не на конкретный вопрос:
Если вы используете компонент COM, который возвращает много данных (скажем, большие массивы 2xN двойников) и требуется только небольшая часть, тогда можно написать компонент COM-оболочки, который скрывает память от .NET и возвращает только те данные, которые необходимо.
Это то, что я сделал в своем основном приложении, и оно значительно улучшило потребление памяти.
источник
Я обнаружил, что использование API-интерфейсов SetProcessWorkingSetSize или EmptyWorkingSet для периодической принудительной загрузки страниц памяти на диск в длительном процессе может привести к тому, что вся доступная физическая память на машине будет эффективно исчезать до тех пор, пока машина не будет перезагружена. У нас была .NET DLL, загруженная в собственный процесс, который будет использовать EmptyWorkingSet API (альтернатива использованию SetProcessWorkingSetSize) для уменьшения рабочего набора после выполнения задачи с интенсивным использованием памяти. Я обнаружил, что где-то от 1 дня до недели машина будет показывать 99% использования физической памяти в диспетчере задач, в то время как никакие процессы не используют сколько-нибудь значительного использования памяти. Вскоре после этого машина перестала отвечать, что требовало полной перезагрузки. Указанные машины представляли собой более двух десятков серверов Windows Server 2008 R2 и 2012 R2, работающих как на физическом, так и на виртуальном оборудовании.
Возможно, это связано с загрузкой кода .NET в собственный процесс, но используйте EmptyWorkingSet (или SetProcessWorkingSetSize) на свой страх и риск. Возможно, использовать его только один раз после первого запуска вашего приложения. Я решил отключить код и позволить сборщику мусора самостоятельно управлять использованием памяти.
источник