Почему JIT_MethodAccessAllowedBySecurity занимает так много времени?

Я работаю над приложением C #, которое позволяет пользователям в основном импортировать таблицы данных, а затем вводить свои собственные формулы на мини-языке для вычисления новых столбцов из базовых данных.

Эти формулы компилируются в деревья выражений LINQ в механизме, которые затем предположительно компилируются в библиотеку деревьев выражений .NET 4.0 в IL, чтобы их можно было выполнить.

Недавно мы начали использовать наш движок для некоторых тикающих данных большого объема, и мы обнаруживаем, что скорость этих скомпилированных деревьев выражений является настоящим узким местом - скорость довольно низкая при пересчете всех этих столбцов на лету. Попадание по нему с помощью встроенного профилировщика Visual Studio 2010 показывает, что половина всего времени выполнения тратится на clr.dll в методе JIT_MethodAccessAllowedBySecurity.

Беглый поиск этой строки ничего не дал, поэтому мне интересно, есть ли там кто-нибудь, кто может сказать мне, что это за метод, и есть ли способ не дать ему съесть все мои циклы? Может быть, есть способ скомпилировать этот код и явно дать ему разрешение делать все, что он хочет, чтобы clr мог остановить эти проверки? Возможно, временные сборки, создаваемые механизмом дерева выражений, не имеют полного доверия?

Во всяком случае, я в значительной степени в растерянности, и мне очень интересно узнать, сталкивались ли другие StackOverflow'еры с этой проблемой в прошлом. Заранее спасибо!


person lvilnis    schedule 30.03.2011    source источник
comment
Можете ли вы опубликовать трассировку стека, которая приведет вас к JIT_MethodAccessAllowedBySecurity?   -  person NightDweller    schedule 10.04.2011
comment
@NightDweller - во встроенном профилировщике VS2010 говорится, что JIT_MethodAccessAllowedBySecurity происходит, когда мы вызываем скомпилированный делегат (а не где-то в теле скомпилированного делегата). Похоже, Джетро на правильном пути, и нам нужно найти способ установить атрибут для самого делегата.   -  person lvilnis    schedule 11.04.2011


Ответы (2)


Решение состоит в использовании LambdaExpression.CompileToMethod (метод MethodBuilder ) вместо LambdaExpression.Compile ( ).

Я думаю, что Джетро был на правильном пути, когда заявил о причастности CAS. При тестировании профилировщик начал показывать вызовы JIT_MethodAccessAllowedBySecurity только тогда, когда я использовал деревья выражений для вызова функций, которые не были динамически определены в сгенерированной сборке (т.е. с помощью Expression.Call для вызова библиотечного метода, а не части сгенерированного кода). предполагает, что замедление было вызвано проверкой CAS, что мой сгенерированный код имел доступ к методам, которые он вызывал. Из этого следует, что, применяя декларативные модификации безопасности к функциям, которые я хотел вызвать, я мог избежать этих накладных расходов.

К сожалению, мне не удалось избавиться от накладных расходов JIT_MethodAccessAllowedBySecurity с помощью декларативной безопасности (PermissionSet, SecurityAction.LinkDemand и т. Д.). В какой-то момент у меня были буквально все методы в моем проекте, отмеченные [PermissionSet (SecurityAction.LinkDemand, Unrestricted = true)], но результатов не было.

К счастью, при поиске способов добавления атрибутов к сгенерированным делегатам я наткнулся на решение - использование MethodBuilder для компиляции дерева выражений, а не встроенного метода LambdaExpression.Compile.

Я включил фрагмент кода, который заменил .Compile () и привел к устранению вызовов JIT_MethodAccessAllowedBySecurity и ускорению> 2x в нашем вычислительном механизме:

// T must be of delegate type (Func<T>, Func<T1, T2>, etc.)
public static T GetCompiledDelegate<T>(Expression<T> expr)
{
    var assemblyName = new AssemblyName("DelegateHostAssembly") { Version = new Version("1.0.0.0") };

    var assemblyBuilder = 
        AppDomain.CurrentDomain.DefineDynamicAssembly(
            assemblyName, 
            AssemblyBuilderAccess.RunAndSave);
    var moduleBuilder = assemblyBuilder.DefineDynamicModule("DelegateHostAssembly", "DelegateHostAssembly.dll");
    var typeBuilder = moduleBuilder.DefineType("DelegateHostAssembly." + "foo", TypeAttributes.Public);
    var methBldr = typeBuilder.DefineMethod("Execute", MethodAttributes.Public | MethodAttributes.Static);

    expr.CompileToMethod(methBldr);

    Type myType = typeBuilder.CreateType();

    var mi = myType.GetMethod("Execute");

    // have to box to object because .NET doesn't allow Delegates as generic constraints,
    // nor does it allow casting of Delegates to generic type variables like "T"
    object foo = Delegate.CreateDelegate(typeof(T), mi);

    return (T)foo;
}

Этот код последовательно> в 2 раза быстрее при использовании любого кода, который использует деревья выражений для вызова функций, которые сами по себе не определены деревьями выражений. Спасибо за помощь, и я надеюсь, что это сэкономит кому-то еще несколько циклов.

person lvilnis    schedule 12.04.2011
comment
@Ivilnis - Отлично - это решение очень помогает в моем случае. Хотел бы я больше понять, почему это необходимо. - person Rob; 02.06.2015
comment
@lvilnis Могу подтвердить, что это волшебная оболочка все еще работает на NET45 при широком использовании деревьев выражений. 30% моего процессорного времени было потрачено на JIT_MemberAccessCheck при использовании профилировщика. После применения этого исправления оно исчезло, и код стал на 30% быстрее. - person Rolf Kristensen; 29.03.2017

Я думаю, это как-то связано с CAS (безопасность доступа по коду).

CAS основан на сборке. Когда вы кодируете вызов защищенного метода, среда выполнения .NET Framework проверяет вашу сборку, чтобы узнать, предоставлены ли ей разрешения или несколько разрешений, необходимых для метода. Затем .NET Framework rutime просматривает стек, чтобы проверить каждую сборку в стеке на наличие этих ошибок. Если одна сборка не имеет всех необходимых разрешений, возникает исключение безопасности и запускается код.

Я думаю, что ниже происходит то, что происходит с вашим кодом.

... происходит обход стека и проверка политики выполняется каждый раз при вызове метода. Это особая проблема для компонентов в библиотеке классов, которая может вызываться много раз. В этой ситуации вы можете использовать запрос ссылки, чтобы указать, что проверка набора разрешений выполняется во время связывания как часть процесса усложнения JIT. Для этого вы украшаете метод с помощью атрибута разрешения, который имеет параметр значения SecurityAction.LinkDemand.

Надеюсь, это поможет, похоже, все, что вам нужно сделать, это установить атрибут SecurityAction.LinkDemand. Цитируемый текст взят из Расширенные основы разработки Microsoft .NET 2.0.

С Уважением

person Jethro    schedule 10.04.2011
comment
Прежде всего, спасибо за помощь, Джетро. К сожалению, SecurityAction.LinkDemand (и большая часть CAS в целом), похоже, были удалены в .NET 4.0 - в VS, когда я наводил указатель мыши на SecurityAction.LinkDemand, он говорит: Не использовать в .NET 4.0. Я думаю, что вы на правильном пути с этой идеей. Я собираюсь начать изучение того, что представляет собой замена .NET 4.0 для LinkDemand, и работает ли CAS по-прежнему таким образом. Отдельный вопрос, который следует из этого, - как добавлять атрибуты к методам, генерируемым деревьями выражений. - person lvilnis; 11.04.2011
comment
System.Linq.Expressions.LambdaExpression.CompileToMethod (метод System.Reflection.Emit.MethodBuilder) похоже, что это может помочь! - person lvilnis; 11.04.2011