Оптимізація запуску додатків

Written on 13 Лютого 2009 . Posted in .NET Framework

ЗМІСТ

Запуски програм зазвичай ділять на холодні та гарячі. Холодний запуск, у контексті керованого додатка, означає, що ні складання системи Microsoft® .NET Framework, ні код програми не завантажені на згадку, і їх потрібно отримати з диска. Гарячий запуск - це або наступний запуск програми, або запуск програми у випадку, коли більшість системного коду вже в пам'яті, тому що використовувався раніше іншим керованим програмою.

Холодний запуск

Найчастіше холодний запуск залежить від вводу-вывода. Іншими словами, більше часу минає при очікуванні даних, ніж при обробці інструкцій. Час запуску програми дорівнює часу, протягом якого операційна система отримує код з диска плюс час, необхідний для виконання додаткової обробки, як JIT-компіляція коду IL або будь-яка інша ініціалізація, що виконується при запуску програми. Так як обробка зазвичай не є обмежує стадією холодного запуску, основним завданням будь-якого дослідження по прискоренню запуску програми є зниження звернень до диска за рахунок зменшення кількості коду, що завантажується.

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

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

З профайлерами на основі вибірки інформація, що збирається, може навіть нашкодити. Тут простежується використання ЦП, а не введення-виведення, тому весь час введення-виведення взагалі не враховується у звітах профайлера.

На рис. 1 ви можете самі переконатися в тому, що холодний запуск залежить від введення-виведення, запустивши свою програму двічі поспіль. Перший запуск буде напевно істотно повільніше другого (при якому більшість коду, що виконується, вже знаходиться в пам'яті після першого запуску, що економить час на звернення до диска). Звичайно, щоб перший запуск дійсно був холодним, потрібно спочатку перезавантажити комп'ютер, переконатися, що в папці запука немає керованих програм і що при вході користувача в систему не запускається служба Windows®, що використовує керований код.

запуску

Рис.1 Час зчитування з диска та час ЦП при холодному запуску

Зауважте, що для ідеального тесту холодного запуску потрібно відключити службу SuperFetch, яка може завантажити частину коду для вашої програми, забезпечуючи більш "теплий" сценарій запуску. Вимірювання при вимкненому SuperFetch дозволяють розраховувати, що весь код програми завантажується в пам'ять при запуску програми, таким чином, витрати на введення-виведення можна оцінити точніше. Слід пам'ятати, що ви вимірюєте не в точності те, що отримає користувач, тому не потрібно робити остаточних висновків про швидкодію програми, виходячи з даних, зібраних при вимкненому SuperFetch.

Два лічильники продуктивності, які можна використовувати, щоб дізнатися вплив холодного запуску на введення-виведення, це % часу використання процесора та % часу зчитування з диска. Якщо введення-висновок надає вирішальний вплив на запуск, чого слід очікувати, між % часу використання процесора і часу зчитування з диска буде велика різниця. Зібрати лічильники продуктивності можна за допомогою PerfMon (докладніше про це див. бічну панель "Матеріали про швидкість запуску").

На рис. 1 червоною лінією позначено % часу зчитування з диска, а зеленою % використання процесора. У разі холодного запуску видно, що використання ЦП невелике порівняно з часом зчитування диска.

При повторному запуску програми є сценарій гарячого запуску, тому лічильник продуктивності повинен показати іншу картину. На рис. 2 процес залежить від ЦП. Можна бачити, що % часу зчитування з диска значно нижче від % часу використання процесора.

додатків

Мал. 2 На гарячий запуск витрачається менше часу

Гарячий запуск пов'язаний з ЦП, оскільки код вже знаходиться в пам'яті (і не потрібне додаткове введення-виведення), але перед запуском програми код потрібно JIT-компілювати. На сьогоднішній день машинний код, створюваний у .NET Framework за рахунок JIT, не зберігається від одного виконання до іншого.

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

Щоб з'ясувати, чи пов'язана проблема з JIT, можна скористатися лічильникомпродуктивності .NET CLR JIT% часу при JIT. Якщо значення невелике (наприклад, переважно більше 30%-40%), це означає, що JIT не має вирішального впливу і потрібно скористатися профайлером, щоб визначити, які функції програми споживають основний час ЦП. Пам'ятайте, що лічильник оновлюється лише тоді, коли методи безпосередньо JIT-компілюються. Це означає, що після JIT-компіляції останнього методу лічильник відображатиме останнє значення, а не обнулиться. Тому на лічильник слід дивитися лише перші кілька секунд запуску програми. У цей час лічильник буде збільшуватись дуже швидко, означаючи, що пік використання ЦП викликаний компілятором JIT.

Визначення коду, що завантажується з диска

Наступний крок - визначити, що завантажується з диска і з'ясувати, чи немає коду, який завантажується без потреби. Найшвидший спосіб визначити, що завантажується в пам'ять - засіб VADump (його можна знайти у Windows Platform SDK). На рис. 3 показаний фрагмент звіту, що створюється при виконанні наступної команди:

Figure 3 VADump Output

Важливо пам'ятати, що VADump показує, що завантажено в пам'ять тільки при роботі засобу, тому воно може пропустити модулі, що завантажуються в пам'ять на короткий час. Він також не показує ту частину програми (код або дані), яка була записана на диск. Завдання полягає в тому, щоб на підставі звіту VADump визначити, чи потрібно завантажувати всі модулі у списку. Наприклад, якщо програма не використовує XML, а System.Xml виявився завантаженим, потрібно розібратися.

З'ясувати, що завантажило збирання, можна за допомогою команди sxe у налагоджувачі Windows (windbg). Команда "sxe ld:" перериває роботу відладчика під час завантаження вказаного DLL. Потім можна перевірити стек викликів та з'ясувати, якафункція завантажила DLL на згадку. Не слід недооцінювати цю частину дослідження. Дуже легко втратити контроль за тим, що програма завантажує на згадку.

Складання системи та інші процеси

Виключивши завантаження зайвих складання при запуску (додатково можна змінити код програми так, щоб відкласти частину ініціалізаційних процесів), наступним кроком потрібно зменшити обсяг коду, що завантажується зі складання системи. Я, на жаль, не знаю засобів, які повідомляли, скільки завантажується коду при використанні системного API. Це було б дуже корисно, тому що розробник міг би використовувати в коді запуску ті API, які завантажують зі збирання системи менше коду. Доки таких засобів немає, можна оцінити приблизне навантаження API за допомогою профайлера на основі інструментів (наприклад, Visual Studio® Performance Tools).

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

У деяких випадках програма може явно або неявно запускати інші процеси при завантаженні. З'ясувати це легко за допомогою параметра –o налагодження Windows (windbg). За рахунок параметра –o налагоджувач приєднується до будь-якого дочірнього процесу. Типовий приклад процесу, що неявно запускається додатком - колиПрограма використовує серіалізацію XML і не прекомпілює класи серіалізації (за допомогою службової програми Sgen). У цій ситуації для їхньої компіляції запускається компілятор C#. Запуск додаткових процесів – це дуже витратна справа, це може мати великий вплив на швидкість завантаження.

Продуктивність NGen

Генератор машинних образів (NGen) завжди допомагає прискорити гарячий запуск, тому що дозволяє уникнути JIT-компіляції коду. NGen може допомогти і в разі холодного запуску, якщо не потрібно завантажувати mscorjit.dll, тому що весь код, що іпсользується додатком, вже заздалегідь скомпільований за допомогою NGen. Однак, якщо хоча б один із модулів не має відповідного машинного образу, mscorjit.dll буде завантажено. Тоді JIT-компілюватиметься код, займаючи цикли ЦП, а також будуть порушені багато сторінок образів NGen, так як компілятор JIT повинен вважати метадані. Час запуску виявиться в результаті ще більшим. Тому варто видалити весь код, який може спричинити JIT-компіляцію при запуску. Звичайно, чи слід використовувати цей підхід, можна вирішити, лише вимірявши швидкість холодного запуску з і без створення машинних образів, тому що підсумкова перевага NGen при холодному запуску залежить від коду та розміру програми; немає гарантій відчутного покращення запуску, навіть якщо виключено JIT-компілювання.

Один із способів визначити, чи відбувається і коли JIT-компілювання, це помічники налагодження керованих додатків (MDA). JIT MDA дозволяє або зупинити налагодження, або вивести інформацію налагодження при JIT-компіляції методу. MDA можна включити, виставивши змінне середовище таким чином:

Додаток зупинить налагоджувач при JIT-компіляції коду. MDA можна також встановити за допомогою реєстру або файлу .config програми. Дізнатись більше проВикористання MDA можна в бічній панелі «Матеріали про швидкість запуску» .

Загалом, щоб NGen справді збільшив швидкість холодного запуску, потрібно забезпечити наступне:

Перевірка Authenticode

Складання можна підписати за допомогою сертифіката Authenticode, використовуючи засіб signcode. Перевірка Authenticode завжди негативно впливає на запуск, оскільки підписані за допомогою Authenticode зборки повинні перевірятись центром сертифікації (CA). Перевірка вимагає повторної перевірки CA, використаного для підпису збірок, а це дуже ресурсомістка операція, пов'язана з доступом до мережі (якщо CA не встановлено локально на тому ж комп'ютері).

Найкраще уникати збірок, підписаних за допомогою Authenticode та використовувати замість них підписи суворими іменами. Якщо підпис Authenticode необхідний, у .NET Framework 3.5 перевірку можна опустити за допомогою наступного параметра конфігурації:

Зауважте, що навіть у тому випадку, коли необхідний підпис Authenticode, час автентифікації можна сильно скоротити, просто встановивши сертифікат CA на комп'ютер клієнта.

Отримати хорошу швидкість холодного запуску можна в першу чергу за рахунок лаконічності коду, що виконується під час запуску. Потрібно відкласти ініціалізацію, яка не є необхідною, перевірити, щоб посилання не завантажувалися занадто рано і спробувати використовувати такі класи та методи, які не завантажують багато коду. Пам'ятайте, що завдання – скоротити звернення до диска. Це нелегко, але Xperf, новий, корисний засіб, включений в новий Windows Server® 2008 SDK, що розробляється, використовує трасування подій для Windows (ETW) для стеження за завантаженими модулями, перемиканнями контексту та іншими подіями, щоб краще визначати, що відбувається при запуску програми . За допомогоюXPerf можна буде збирати дуже точні дані про час запуску додатків. Бічна панель "Матеріали про швидкість запуску" містить посилання на додаткову корисну інформацію.

Клаудіо Калдато (Claudio Caldato) керівник проекту розробки Складальника сміття в команді CLR корпорації Майкрософт.