Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.14.3. Схема трансляции программ в .Net.

Компилятор JIT является ключевым средством платформы .NET и жизненно важной со­ставляющей в реализации попытки Microsoft сделать так, чтобы управляемый код работал с большей производительностью, чем неуправляемый код.

Проблема производительности возникает из-за того, что код компилируется в промежу­точный язык, и это может стать неожиданностью для некоторых разработчиков, В конце концов, одним из недостатков Jaya является то, что процесс трансляции с байт-кода Java в исполняемый код в процессе выполнения программы означает потерю производительно­сти. Однако существует все же большая разница, заключающаяся в том, что байт-код Java интерпретируется, a IL компилируется. Более того, JIT-компилятор компилирует не всю программу сразу (что может привести к излишне долгому запуску программы), а лишь кусок кода и именно тогда, когда он вызывается (отсюда название JIT-компилятора: just-in-time — вовремя). После того как код единожды откомпилирован, получившийся в результате машинный код сохраняется в памяти до тех пор, пока не будет осуществлен выход из программы, поэтому при следующем запуске того же самого кода его уже не потребуется компилировать. Microsoft считает, что это более эффективный процесс, чем компиля­ция всей сборки сразу, так как велика вероятность того, что большая часть кода сборки не будет выполняться во время одного конкретного запуска программы. При использо­вании JIT- компилятора такой код вообще не придется компилировать.

Этим объясняется, почему исполнение управляемого кода на IL будет почти таким же быстрым, как и исполнение родного маши иного кода, но не объясняется, почему Microsoft ожидает получить еще и улучшение по производительности. А дело в следую­щем: так как окончательная стадия компиляции происходит уже в процессе исполнения, JIT-компилятор точно знает, на каком процессоре будет работать программа. Это означа­ет, что код может быть оптимизирован под использование тех особенностей, которые предлагаются каждым конкретным процессором.

Традиционные компиляторы оптимизируют код, но они могут выполнять только оп­тимизацию, не зависящую от конкретного процессора. Это происходит из-за того, что современные компиляторы осуществляют компиляцию сразу в машинный код перед тем, как программа будет отправлена потребителю. Следовательно, компилятор не зна­ет, на каком процессоре будет исполняться код. Ему могут быть известны лишь общие сведения, что это будет процессор семейства х86 или процессор Alpha. Visual Studio 6, например, оптимизирует код для процессора Pentium, поэтому такой код не сможет вос­пользоваться преимуществами процессора Pentium III. Напротив, JIT-компилятор мо­жет выполнить ту же оптимизацию, что и Visual Studio 6, но вдобавок оптимизировать код для того процессора, на котором он будет исполняться.

4.14.4. Язык msil.

Часто CIL называют псевдоассемблером, так как он определяет набор команд не­коего процессора. Однако в данном случае процессором является не кусок крем­ния, a CLR. При написания программ для .NET Framework знать CIL нужно не бо­лее, чем ассемблер х8б для программирования под Windows. И все же элементар­ные познания в СП. могут быть полезны, если вам понадобится узнать, почему какой-то метод FCL работает не так, как вы этого ожидали. У вас нет исходного кода FCL. но есть CIL CIL содержит примерно 100 команд. Некоторые из них — типичные низкоуров­невые, аналогичные командам микропроцессоров, например, команды сложения двух значений (ADD) или перехода, если два значения равны (BEQ). Другие, бо­лее высокого уровня, редко встречаются в аппаратных наборах команд. Так, NEWOBJ создает экземпляр объекта, a THROW генерирует исключение. Благодаря такому богатству набора команд CIL, код на языке высокого уровня, таком как С» или Visual Basic .NET, зачастую порождает при компиляции удивительно малое число команд.

CIL использует стековую модель исполнения. Если процессоры х8б для обра­ботки значений загружают их в регистры, то CLR помещает их в вычислительный стек. Чтобы сложить два числа, они копируются в стек, вызывается ADD, и результат считывается из стека. Копирование значения из памяти в стек называется загруз­кой (loading), а копирование в обратном направлении — сохранением (storing). В СП, есть несколько команд загрузки и сохранения. Например, LDLOC загружает в стек значение по некоторому адресу в памяти, a STLOC копирует значение из стека в память, удаляя его из стека,

В качестве примера работы C1L, рассмотрим фрагмент программы С#, в кото­ром объявляются и инициализируются две переменные, затем они складывают­ся, и сумма записывается в третью переменную;

int а = 3; irt b = 7;

irt с = а + Ь;

Ниже приведен CIL, сгенерированный компилятором Microsoft C#:

ldc.14.3 // Загрузить в стек 32-разрядное (14) число 3.

stJ.oc.0 // Сохранить его в локальной переменной 0 (а).

ldc.14,7 // Загрузить на отек 32-разрядное (14) число 7.

stloc.1 // Сохранить его в локальной переменной 1 (Ь).

ldloc.0 // Загрузить в стек локальную переменную 0.

1йЮс,1 // Загрузить в стек локальную переменную 1.

add // Сложить два числа и получить сумму в стеке.

stloc.2 // Сохранить сумму в локальной переменной 2 (с).

Как видите, CIL весьма прост. Однако непонятно, как выделяется память для ло­кальных переменных а, Ь и с (для CIJR это локальные переменные О, I и 2). С по­мощью метаданных. При компиляции в метаданные метода помещаются сведения о том, что объявлены три локальных 32-разрядных целых переменных, CLR счи­тывает эти сведения и выделает память для локальных переменных перед нача­лом исполнения метода. Если дисассемблировать метод с помощью ILDASM, ме­таданные будут отображены как директива компилятора:

.locals init (int32 V_0, // Локальная переменная 0 (а). int32 V_1, // Локальная переменная 1 (Ь). int32 V_2) // Локальная переменная 2 (с).

Это превосходный пример того, как важны метаданные /тля CLR. Они служат не только для проверки безопасности типов, но и для подготовки контекста испол­нения. Кстати, если исполняемый модуль С* скомпилирован с ключом /DEBUG, то ILDASM отобразит настоящие названия переменных вместо условных обозна­чений вроде V_0.

В .NET Framework SDK есть документ, описывающий весь набор команд СП. в мельчайших подробностях. Не буду помещать здесь полный список команд CIL — приведу лишь наиболее часто используемые команды и их краткое описание.

ILDASM, которая позволяет посмотреть метаданные, является и прекрасным дизассемблером CIL, Запустите ILDASM и откройте с ее помощью одну из библиотек Systcm.'.dll в каталоге \%SystemRoot%\Microsoft.NET\Framework\vl.0.mmK. Эти DLL относятся к библиотеке классов .NET Framework. Затем выберите какой-нибудь метод для дезассемблирования. Методы легко отыскать — они обозначены малино­выми прямоугольниками. Дважды щелкнув метод, вы увидите его CIL вместе с директивами компилятора, сгенерированными по метаданным метода. Более того, ILDASM — это двунаправленный дизассемблер, т. е. ему на вход можно подать дисассемблированный код и снова получить CIL

Разработчики часто поднимают проблему интеллектуальной собственности; если каждый может дезассемблировать FCL, что помешает конкуренту дизассемблировать чужой продукт? Восстановление исходного текста no CIL — нетриви­альная задача, но это проще, чем восстановить его по коду х86. Кроме того, де­компиляторы, генерирующие по CIL исходный текст на С#, имеются в свободном доступе в Интернете, Как же защитить свою интеллектуальную собственность?

Короткий ответ — по обстоятельствам. Код, исполняемый только на серверах, например, Web-сервисы XML, недоступен пользователям и, таким образом, не может быть дизассемблирован, если только кто-нибудь не проникнет сквозь ваш бранд­мауэр. Код, поставляемый конечным пользователям, может быть зашифрован утилитами.