Оптимизация запуска приложений .NET

ОГЛАВЛЕНИЕ

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

Запуски приложений обычно делят на холодные и горячие. Холодный запуск, в контексте управляемого приложения, значит, что ни сборки системы 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.

Следует иметь в виду, что любое приложение, запускающееся при входе пользователя в систему, вынуждено конкурировать за ввод-вывод с другими службами и приложениями, что еще больше увеличивает время запуска. Поэтому старайтесь не добавлять приложения в группу запуска при загрузке (хорошее средство определить, какие приложения запускаются при загрузке компьютера -- AutoRuns, которое можно найти по адресу microsoft.com/technet/sysinternals/Security/Autoruns.mspx).