Могу ли я заставить компилятор оптимизировать определенный метод?

Есть ли атрибут, который я могу использовать, чтобы сообщить компилятору, что метод всегда должен быть оптимизирован, даже если глобальный переключатель /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 С#, основанный на оптимизации хвостового вызова (чтобы избежать), просто сломан. В оптимизации хвостовых опций С# есть только оптимизация производительности.

Использовать язык, на котором вызовы хвоста испускаются надежно, или переписать свой метод, чтобы он не нуждался в хвостовых вызовах.