Рабочим названием платформы .NET было |
Динамическая генерация кода
Введение в динамическую генерацию кода
Динамическая генерация кода - это прием программирования, заключающийся в том, что фрагменты кода порождаются и запускаются непосредственно во время выполнения программы. Этот прием был известен достаточно давно, но усложнение архитектуры компьютеров, и, что особенно важно, усложнение наборов команд процессоров привело к тому, что в последние 10-15 лет динамическая генерация кода в некоторой степени потеряла популярность.
Целью динамической генерации кода является использование информации, доступной только во время выполнения программы, для повышения качества исполняемого кода. В терминах метавычислений можно сказать, что динамическая генерация кода позволяет специализировать фрагменты программы по данным, известным во время выполнения.
В некотором смысле, любой JIT-компилятор как раз использует динамическую генерацию кода: имея некоторую программу, записанную на промежуточном языке (байт-коде), и зная, какой процессор работает в системе, JIT-компилятор динамически транслирует программу в инструкции этого процессора. При этом можно считать, что тип процессора - эта как раз та часть информации, которая становится известной только во время выполнения программы.
Естественно, не стоит чересчур увлекаться динамической генерацией кода: этот прием далеко не всегда дает ускорение программы. Можно сказать, что применение динамической генерации оправдано, если:
- процесс вычислений в некотором фрагменте программы преимущественно определяется информацией, известной только во время выполнения;
- запуск этого фрагмента осуществляется многократно;
- выполнение фрагмента связано с существенными затратами времени процессора.
В .NET доступно два способа организации динамической генерации кода:
- порождение программы на языке C# и вызов компилятора C#;
- непосредственное порождение метаданных и CIL-кода.
Если сравнить эти два способа, можно придти к выводу, что порождение C#-программы несколько проще, нежели генерация CIL-кода. Однако, наличие в библиотеке классов пространства имен System.Reflection.Emit позволяет избежать рутинных и трудоемких операций по работе с физическим представлением метаданных и CIL-кода. Кроме того, генерация CIL-кода выполняется на порядок быстрее и дает большую гибкость. Поэтому для программиста, знакомого с набором инструкций CIL, второй способ является более предпочтительным.
В этом разделе мы рассмотрим простой пример программы на языке C#, выполняющей численное интегрирование функции, которую пользователь вводит с клавиатуры (то есть интегрируемая функция становится известной только в процессе выполнения программы). Исходный код примера приведен в Приложении B. Характерной особенностью задачи численного интегрирования является необходимость многократного вычисления значения функции в разных точках. При этом, так как функция представлена в виде строки, это вычисление связано со значительными затратами времени процессора. Таким образом, данная задача по всем признакам подходит для использования динамической генерации кода.
Мы будем выполнять вычисление значения функции тремя способами:
- Без динамической генерации кода (путем непосредственной интерпретации выражения).
- Путем динамической генерации программы на языке C#.
- Путем динамической генерации метаданных и CIL-кода.
Затем мы сравним эффективность каждого способа.