Пакетная компиляция в ASP.NET (исходники)Источник: GOTDOTNET Дмитрий Мартынов
Наверное многие замечали, что первое обращение к ASPX странице занимает значительно больше времени, чем последующие и при этом заметно повышенное потребление ресурсов Веб-сервера. Это ASP.NET производит т.н. динамическую компиляцию или компиляцию "на-лету" (on-the-fly). Она происходит автоматически, когда браузер запрашивает страницу с сервера. Очевидно, что хотелось бы иметь средство избежать подобных задержек. Далее я рассмотрю, что же предлагает ASP.NET для борьбы с этим, а так же приведу некоторые детали того, как происходит обработка запросов к страницам с точки зрения динамической компиляции. Немного теорииКак известно, ASP.NET в отличие от ASP производит компиляцию (а не интерпретацию) ASPX страниц. При такой компиляции для каждой страницы или группы страниц создается сборка (assembly) - динамическая библиотека dll. Замечу, что речь идет именно о страницах ASPX и серверных скриптах внутри этих страниц, а не о т.н. code behind файлах, которые должны быть заранее скомпилированы и размещены в папке BIN или глобальном кеше сборок (GAC - Global Assembly Cache). Использование code behind файлов является предпочтительным способом, так как позволяет добиться разделения кода и представления. К сожалению это не исключает компиляции ASPX страниц. Сборка содержит MSIL (MicroSoft Intermediate Language) код, который в свою очередь перед фактическим исполнением компилируется в исполняемый код. Единица компиляции - вызываемый метод. Это т.н. JIT-компиляция. Соственно говоря, этим уже занимается не ASP.NET, а.NET Framework, так как с точки зрения Framework скомпилированная ASPX страница - это обычная сборка, содержащая несколько классов, поддерживающих определенные интерфейсы. После JIT-компиляции исполняемый код метода кешируется и последующие обращения к этому методу происходят напрямую. Таким образом имеются две области, в которых происходят задержки при обращении к ASPX странице:
Последнюю мы рассматривать не будем, а вот в процесс компиляции "на-лету" можно вмешаться. Первый запрос страницыASP.NET определяет, что требуется компиляция страницы, в случае, если для нее нет сохраненной сборки или страница была изменена. Далее производятся две основные операции:
Если страница уже была ранее скомпилирована, т.е. для нее есть соответствующая сборка, то данная сборка используется в качестве обработчика запрошенной страницы. Давайте представим себе реальную ситуацию, когда Веб-сайт содержит несколько сотен или тысяч страниц. Что происходит, когда пользователь запрашивает страницу с сервера? Правильно - компиляция этой страницы. Теперь представьте, что сайт нагружен. Очевидно, что в этом случае время отклика сервера возрастает, а это не есть хорошо. Кстати - это касается и Веб-сервисов, но это немного другая история, так как здесь вмешиваются механизмы Reflection и сериализации. Что же можно сделать, чтобы оптимизировать процесс динамической компиляции? ASP.NET предлагает средство, называемое "пакетная компиляция" (batch compilation). Пакетная компиляцияЗа процесс компиляции отвечает элемент конфигурации <compilation> файла web.config:
Чтобы включить эту опцию, надо установить атрибут batch равным true, а debug равным false.
Что же произойдет в этом случае ? При запросе любой страницы из каталога, ASP.NET скомпилирует все страницы и создаст одну сборку. Т.е. вместо множества небольших dll (одна dll для одной страницы) получится одна большая dll для всех страниц в этом каталоге. Здесь надо заметить, что пакетная компиляция производится на основе каталога, а не для всего Веб-приложения. Очевидно преимущество такого подхода - не надо компилировать и разбирать страницу за страницей при первом к ним обращении. Но как говорится, бесплатный сыр бывает только в мышеловках. И данный подход - не исключение. Отрицательная сторона заключается в том, что такая компиляция занимает больше ресурсов сервера и может увеличить время отклика страниц, участвующих в такой компиляции. Но на это есть свое противоядие - "пред-пакетная" компиляция (pre-batch compilation). Т.е. перед тем, как кто-либо запросит любую страницу с сервера, Вы сами обращаетесь к любой странице, тем самым инициируя процесс пакетной компиляции и заставляя ASP.NET создать и закешировать все страницы сайта. Отсюда мораль - всегда перед передачей Веб-приложения в "производство" запрашивайте страницы из всех виртуальных каталогов.
Технические деталиТеперь немного о технических деталях. Любая страница ASPX при обращении к ней автоматически компилируется и попадает в: %systemroot%/Microsoft.NET/Framework/<версия>/Temporary ASP.NET Files/<сайт>/nnnnn/mmmm (nnnnn и mmmm - случайные последовательности). Часть пути "%systemroot%/Microsoft.NET/Framework/<версия>/Temporary ASP.NET Files" может быть изменена с помощью атрибута tempDirectory На моем компьютере - это: C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files\TestProject\b5afd0e9\e951ffc7 В зависимости от значения атрибута batch, там будет находиться один или более файлов вида aabbcc.dll, а так же настройки для связывания (Test.aspx.mmmmm.xml). Хитрость заключается в том, что в случае с batch=true, там будет всего одна dll, но в настройках связывания (напоминаю - это файлы вида Test.aspx.mmmmm.xml) будет указана одна и та же dll для всех страниц. Название dll выбирается случайным образом, аналогично выбору имени временного файла. Также эта директория (а точнее поддиректория assembly) используется для т.н. "теневого" копирования используемых в Веб-приложении dll, что позволяет ASP.NET не блокировать их (вспомните ASP и COM). Что происходит, если меняется одна страницаА теперь - самое интересное. Вы включили batch=true, произвели предкомпиляцию и наслаждаетесь быстрой работой сайта. В этот момент оказывается, что необходимо исправить одну страницу, скажем Test.aspx. Что происходит при следующем запросе этой страницы ? ASP.NET компилирует ее и создает отдельную сборку со случайным именем, например xxyyzz.dll. Потом меняется настройка связывания, так что теперь ASP.NET знает: "код выполнения страницы Test.aspx находится не в aabbcc.dll, а в xxyyzz.dll". При этом в старой сборке остается код для страницы Test.aspx, но он не используется. Как устроена страница или магия в действииЕсли заглянуть в xxyyzz.dll с помощью утилиты ildasm (входит в состав .NET Framework SDK; также устанавливается с Visual Studio.NET), то можно убедиться, что в ней находится класс Test_aspx, который наследован от того класса, который вы использовали в качестве code behind (Test.aspx.cs, если таковой имелся, что не является обязательным; это класс, про который директива @Page в начале Вашей странице сообщает: Codebehind="Test.aspx.cs" Inherits="TestProject.Test"), который в свою очередь наследован от System.Web.UI.Page - базовый обработчик ASPX страниц (т.е. класс, поддерживающий интерфейс IHttpHandler и участвующий в конвеере запросов IIS/ASP.NET). Для лучшего понимания процесса надо установить debug=true. Замечу, что при этом перестанет работать пакетная компиляция, но это дает нам возможность увидеть, как наша страница выглядит в C#. Тем, кто не собирается заниматься подобными изысканиями могу сообщить, что в основном там следующее:
Если запустить Task Manager, то можно заметить, как процесс csc.exe появляется и исчезает. Это запускается компилятор C#, который собственно и производит компиляцию исходного файла, полученного в результате разбора страницы. После чего данная сборка используется конвеером ASP.NET для обработки запроса на ресурс Test.aspx. ЗаключениеНесмотря на то, что при программировании ASP.NET не требуется знание работы среды исполнения, но в реальных проектах часто требуется тонкая настройка Веб-сервера, особенно под нагрузкой. Надеюсь, что информация в данной статье поможет разработчикам лучше понять работу ASP.NET и, возможно, решить некоторые проблемы, в частности, с ошибками по правам доступа, кешированием компонентов и т.п. |