Рабочим названием платформы .NET было |
Динамическая генерация кода
Трансляция выражений в CIL
Более сложный, но эффективный способ динамической генерации кода предоставляется классами, относящимися к пространству имен System.Reflection.Emit. Эти классы в нашем примере используются в статическом методе CompileToCIL, который осуществляет трансляцию выражения напрямую в CIL:
static Function CompileToCIL(Expression expr)
Метод начинается с создания заготовки для будущей динамической сборки. Сборка будет выполняться в том же домене приложений, что и основная программа, поэтому объектную ссылку на домен приложений мы получаем путем вызова статического метода Thread.GetDomain. Затем вызываем метод DefineDynamicAssembly домена приложений и получаем объект класса AssemblyBuilder, позволяющий строить динамическую сборку:
AppDomain appDomain = Thread.GetDomain(); AssemblyName assemblyName = new AssemblyName(); assemblyName.Name = "f"; AssemblyBuilder assembly = appDomain.DefineDynamicAssembly( assemblyName, AssemblyBuilderAccess.RunAndSave );
Теперь мы можем создать в сборке модуль и добавить в него класс FunctionCIL, наследующий от класса Function. Обратите внимание, что при генерации кода через классы пространства имен System.Reflection.Emit явно прописывать импортируемые сборки не надо (например, не надо прописывать сборку Integral.exe, из которой импортируется класс Function ), так как это выполняется автоматически:
ModuleBuilder module = assembly.DefineDynamicModule("f.dll", "f.dll"); TypeBuilder typeBuilder = module.DefineType( "FunctionCIL", TypeAttributes.Public | TypeAttributes.Class, typeof(Function) );
В каждом классе должен быть конструктор. Компилятор C# создает конструкторы без параметров по умолчанию, поэтому при генерации C#-кода нам не надо было явно объявлять конструктор в классе FunctionCS. Однако, при генерации динамической сборки через классы пространства имен System.Reflection.Emit конструкторы автоматически не добавляются, и нам придется сделать это самостоятельно:
ConstructorBuilder cons = typeBuilder.DefineConstructor( MethodAttributes.Public, CallingConventions.Standard, new Type[] { } ); ILGenerator consIl = cons.GetILGenerator(); consIl.Emit(OpCodes.Ldarg_0); consIl.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); consIl.Emit(OpCodes.Ret);
Разобравшись с конструктором, переходим к методу Eval. Как уже говорилось, код этого метода почти полностью генерируется в методе GenerateCIL выражения, остается лишь добавить в конец инструкцию ret:
MethodBuilder evalMethod = typeBuilder.DefineMethod( "Eval", MethodAttributes.Public | MethodAttributes.Virtual, typeof(double), new Type[] { typeof(double) } ); ILGenerator il = evalMethod.GetILGenerator(); expr.GenerateCIL(il); il.Emit(OpCodes.Ret);
Итак, мы закончили формирование класса FunctionCIL. Осталось создать для него объект рефлексии и через этот объект вызвать конструктор:
Type type = typeBuilder.CreateType(); ConstructorInfo ctor = type.GetConstructor(new Type[0]); return ctor.Invoke(null) as Function;
Таким образом, получается объект класса FunctionCIL, который в дальнейшем можно использовать для вычисления значения функции в процессе интегрирования.
Сравнение эффективности трех способов вычисления выражений
Давайте оценим эффективность рассмотренных способов вычисления выражений при интегрировании. Для этого будем интегрировать функцию "2*x*x*x+3*x*x+4*x+5" от 0.0 до 10.0 с 10000000 разбиений.
В таблице 5.1 представлены результаты измерений, проведенных на компьютере с процессором Intel Pentium 4 с тактовой частотой 3000 МГц и 1 Гб оперативной памяти.
Способ вычисления значения функции | Время на создание динамической сборки, мс | Время вычисления интеграла функции, мс |
---|---|---|
Интерпретация дерева выражения | - | 29422 |
Предварительная компиляция С# | 547 | 172 |
Предварительная компиляция в CIL | 63 | 172 |
Результаты показывают, что динамическая генерация кода может на два порядка уменьшить время работы программы.