Могу ли я заставить компилятор оптимизировать определенный метод?
Есть ли атрибут, который я могу использовать, чтобы сообщить компилятору, что метод всегда должен быть оптимизирован, даже если глобальный переключатель /o+
не установлен?
Причина, о которой я прошу, заключается в том, что я играю с идеей динамического создания метода на основе кода IL существующего метода; манипуляция, которую я хочу сделать, достаточно проста, когда код оптимизирован, но становится значительно сложнее в неоптимизированном коде из-за дополнительных инструкций, сгенерированных компилятором.
EDIT: более подробная информация о не оптимизациях, которые меня беспокоят...
Рассмотрим следующую реализацию факториальной функции:
static long FactorialRec(int n, long acc)
{
if (n == 0)
return acc;
return FactorialRec(n - 1, acc * n);
}
(Примечание: я знаю, что есть более эффективные способы вычисления факториала, это просто пример)
IL, сгенерированный с включенными оптимизациями, довольно прост:
IL_0000: ldarg.0
IL_0001: brtrue.s IL_0005
IL_0003: ldarg.1
IL_0004: ret
IL_0005: ldarg.0
IL_0006: ldc.i4.1
IL_0007: sub
IL_0008: ldarg.1
IL_0009: ldarg.0
IL_000A: conv.i8
IL_000B: mul
IL_000C: call UserQuery.FactorialRec
IL_0011: ret
Но неоптимизированная версия совсем другая
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldc.i4.0
IL_0003: ceq
IL_0005: ldc.i4.0
IL_0006: ceq
IL_0008: stloc.1
IL_0009: ldloc.1
IL_000A: brtrue.s IL_0010
IL_000C: ldarg.1
IL_000D: stloc.0
IL_000E: br.s IL_001F
IL_0010: ldarg.0
IL_0011: ldc.i4.1
IL_0012: sub
IL_0013: ldarg.1
IL_0014: ldarg.0
IL_0015: conv.i8
IL_0016: mul
IL_0017: call UserQuery.FactorialRec
IL_001C: stloc.0
IL_001D: br.s IL_001F
IL_001F: ldloc.0
IL_0020: ret
Он предназначен для того, чтобы иметь только одну точку выхода, в конце. Возвращаемое значение сохраняется в локальной переменной.
Почему это проблема? Я хочу динамически генерировать метод, который включает оптимизацию хвостового вызова. Оптимизированный метод можно легко изменить, добавив префикс tail.
перед рекурсивным вызовом, поскольку после вызова не существует ничего, кроме ret
. Но с неоптимизированной версией я не уверен, что результат рекурсивного вызова хранится в локальной переменной, тогда есть бесполезная ветвь, которая просто переходит к следующей инструкции, локальная переменная загружается и возвращается. Поэтому у меня нет простого способа проверить, что рекурсивный вызов действительно является последней инструкцией, поэтому я не могу быть уверен в том, что оптимизация хвостового вызова может быть применена.
Ответы
Ответ 1
Если метод, который вы будете использовать в качестве шаблона для динамического метода, будет относительно простым - и без зависимостей от других методов. Затем просто поместите его в свою сборку и включите оптимизацию только для этой сборки.
Что касается исходной проблемы, поскольку MSIL является языком на основе стека. И спецификации гарантируют состояние стека в инструкции ret
, на которой вы можете быть на 100% уверены, что вы можете добавить префикс хвоста без проблем. Тем не менее, это также вряд ли реально добавит какой-либо выгоды, поскольку я не видел, чтобы JIT использовал префикс хвоста, чтобы фактически оптимизировать окончательно закодированный код.
Ответ 2
Есть ли способ генерировать исходный код метода динамически с помощью Microsoft.CSharp.CSharpCodeProvider?
Если вы контролируете компиляцию метода, вы можете установить параметры при вызове компилятора с помощью CompilerOptions.
Ответ 3
Вы никогда не сможете быть уверены в том, что получите оптимизацию хвостовых вызовов, если вы используете С#.
В частности, даже при call ... ret
JITter не гарантирует хвостовой вызов. Поэтому код IMO С#, основанный на оптимизации хвостового вызова (чтобы избежать), просто сломан. В оптимизации хвостовых опций С# есть только оптимизация производительности.
Использовать язык, на котором вызовы хвоста испускаются надежно, или переписать свой метод, чтобы он не нуждался в хвостовых вызовах.