Зменшення використання пам'яті додатків .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);
}

Результати випуску x86 за межами Visual Studio:

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

Що трохи краще, але все ж здається надмірним для такої простої програми. Чи є якісь хитрощі, щоб зробити процес C # трохи піснішим? Я пишу програму, розроблену для роботи у фоновому режимі більшу частину часу. Я вже займаюся будь-якими елементами інтерфейсу користувача в окремому Домені додатка, що означає, що речі інтерфейсу користувача можна безпечно вивантажувати, але зайняття 10 Мб, коли він просто сидить на задньому плані, здається зайвим.

PS Щодо того, чому я б переймався --- (Power) користувачі, як правило, хвилюються з приводу цих речей. Навіть якщо це майже не впливає на продуктивність, користувачі, які сприймають напівтехнології (моя цільова аудиторія), як правило, впадають у шикарну залежність від використання фонової пам’яті програм. Навіть я злякаюсь, коли бачу, як Adobe Updater займає 11 Мб пам'яті і заспокоюється заспокійливим дотиком Foobar2000, який може займати менше 6 МБ навіть під час гри. Я знаю, що в сучасних операційних системах цей матеріал насправді не так важливий технічно, але це не означає, що він не впливає на сприйняття.


14
Чому б ти хвилювався? Приватний робочий набір досить низький. Сучасні ОС будуть виводити на диск, якщо пам'ять не потрібна. Це 2009. Якщо ви не будуєте речі на вбудованих системах, вам не варто піклуватися про 10 Мб.
Мехрдад Афшарі

8
Перестаньте використовувати .NET, і ви можете мати невеликі програми. Щоб завантажити .NET Framework, в пам'ять потрібно завантажити багато великих DLL-файлів.
Адам Підвіконня

2
Ціни на пам'ять падають експоненціально (так, зараз можна замовити домашню комп'ютерну систему Dell з 24 ГБ оперативної пам’яті). Якщо ваша програма не використовує> 500MB оптимізація не потрібна.
Алекс

28
@LeakyCode Я дуже ненавиджу, що сучасні програмісти думають таким чином, вам слід подбати про використання пам'яті вашої програми. Я можу сказати, що більшість сучасних додатків, написаних здебільшого на Java або c #, є досить неефективними, коли справа стосується управління ресурсами, і завдяки цьому в 2014 році ми можемо запустити стільки ж додатків, скільки ми могли б ще в 1998 році на win95 і 64mb оперативної пам’яті ... лише 1 примірник браузера з'їдає 2 Гб оперативної пам’яті, а простий IDE - близько 1 ГБ. Баран дешевий, але це не означає, що варто витрачати його.
Петро

6
@Petr Вам слід подбати про управління ресурсами. Час програміста теж ресурс. Будь ласка, не надмірно узагальнюйте, витрачаючи 10 Мб на 2 Гб.
Мехрдад Афшарі

Відповіді:


33
  1. Можливо, ви хочете перевірити запит про переповнення стека. NET EXE .
  2. Повідомлення в блозі MSDN Робочий набір! = Фактичний слід пам’яті - це демістифікація робочого набору, обробка пам’яті та способи точних обчислень загального споживання в оперативній пам’яті.

Я не скажу, що вам слід ігнорувати слід пам’яті вашої програми - очевидно, що менші та ефективніші, як правило, бажані. Однак ви повинні врахувати, які ваші фактичні потреби.

Якщо ви пишете стандартні клієнтські програми Windows Forms та WPF, призначені для роботи на персональному ПК, і, швидше за все, це основний додаток, в якому користувач працює, ви можете уникнути, коли ви будете більш нединамічними щодо розподілу пам'яті. (До тих пір, поки все це стане розмещеним.)

Однак, щоб звернутися до деяких людей, які кажуть, що не турбуватися про це: Якщо ви пишете програму Windows Forms, яка працюватиме в середовищі термінальних служб, на спільному сервері, можливо, використовується 10, 20 або більше користувачів, тоді так , ви абсолютно повинні врахувати використання пам'яті. І вам потрібно буде бути пильними. Найкращий спосіб вирішити це - за допомогою хорошого дизайну структури даних та дотримуючись кращих практик щодо того, коли і що ви виділяєте.


45

.NET-додатки матимуть більший слід порівняно з рідними програмами через те, що вони обидва повинні завантажувати час виконання та додаток у процесі. Якщо ви хочете щось по-справжньому охайне, .NET може бути не найкращим варіантом.

Однак майте на увазі, що якщо ваша програма в основному спить, потрібні сторінки пам’яті будуть витіснені з пам’яті, і таким чином насправді це не буде великим тягарем для системи в основному.

Якщо ви хочете, щоб слід було невеликим, вам доведеться подумати про використання пам'яті. Ось пара ідей:

  • Зменшіть кількість об’єктів і переконайтеся, що не тримайте жоден екземпляр довше, ніж потрібно.
  • Будьте в курсі List<T>та подібних видів, які збільшують подвійну потужність, коли це потрібно, оскільки вони можуть призвести до 50% відходів.
  • Ви можете розглянути можливість використання типів значень над еталонними типами, щоб примусити більше пам’яті на стеці, але майте на увазі, що за замовчуванням стек простору становить лише 1 Мб.
  • Уникайте об'єктів, що мають більше 85000 байт, оскільки вони будуть переходити до LOH, який не ущільнюється, і, таким чином, може легко фрагментуватися.

Це, мабуть, не вичерпний перелік будь-якими способами, а лише пара ідей.


IOW, такі самі методи, які працюють для зменшення розміру нативної коду, працюють у .NET?
Роберт Фрейзер

Напевно, є певне перекриття, але з нативним кодом у вас є більше варіантів, коли мова йде про використання пам'яті.
Брайан Расмуссен

17

У цьому випадку потрібно враховувати вартість пам'яті CLR. CLR завантажується для кожного процесу .Net і, отже, факторів, що враховуються в пам'яті. Для такої простої / невеликої програми вартість CLR буде домінувати у вашій пам'яті.

Було б набагато доцільніше побудувати реальну програму та переглянути її вартість порівняно з ціною цієї базової програми.


7

Ніяких конкретних пропозицій сама по собі немає, але ви можете поглянути на CLR Profiler (безкоштовно завантажити з Microsoft).
Після встановлення перегляньте цю практичну сторінку .

З інструкцій:

У цьому розділі "Як показати", як використовувати інструмент CLR Profiler для дослідження профілю розподілу пам'яті програми. Ви можете використовувати CLR Profiler для виявлення коду, який спричиняє проблеми з пам'яттю, такі як витоки пам'яті та надмірне чи неефективне збирання сміття.


7

Можливо, хочете подивитися на використання пам'яті "реального" додатка.

Подібно до Java, існує деяка фіксована кількість накладних витрат на час виконання незалежно від розміру програми, але споживання пам'яті буде набагато розумнішим після цього моменту.


4

Є ще способи зменшити приватний робочий набір цієї простої програми:

  1. NGEN ваша заявка. Це знімає вартість компіляції JIT з вашого процесу.

  2. Навчіть свою програму, використовуючи MPGO, зменшуючи об'єм пам'яті, а потім NGEN.


2

Існує багато способів зменшити свій слід.

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

І цей код не можна повністю поділити між екземплярами програми. Навіть збірки NGEN не є повністю статичними, у них все ще є невеликі деталі, які потребують JITting.

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

Приклад, який часто бачимо: беручи Datareader, завантажуючи вміст у DataTable просто для того, щоб записати його у XML-файл. Ви можете легко зіткнутися з OutOfMemoryException. OTOH, ви можете використовувати XmlTextWriter і прокручувати Datareader, випромінюючи XmlNodes під час прокрутки курсору бази даних. Таким чином, у вас є тільки поточна запис бази даних та її вихід XML в пам'ять. Що ніколи (або навряд чи вдасться) отримати більш високу генерацію сміття, і таким чином може бути використане повторно.

Це ж стосується отримання списку деяких екземплярів, виконання певних речей (що породжує тисячі нових екземплярів, які можуть десь посилатись), і хоч вони вам не потрібні згодом, ви все одно посилаєтесь на них до наступного виступу. Якщо явно скасовується ваш список введення даних і ваші тимчасові побічні продукти, цю пам'ять можна повторно використовувати навіть до виходу з циклу.

C # має чудову функцію, яку називають ітераторами. Вони дозволяють потоково передавати об'єкти, прокручуючи вхід, і зберігати лише поточний екземпляр, поки ви не отримаєте наступний. Навіть використовуючи LINQ, вам все одно не потрібно тримати все навколо лише тому, що ви хотіли, щоб воно було відфільтровано.


1

Розгляд загального питання в заголовку, а не конкретного питання:

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

Це я зробив у своєму головному застосуванні, і це значно покращило споживання пам'яті.


0

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

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.