Проверка границ массива в DynamicAssembly работает только тогда, когда стек оценки пуст

У меня есть простой цикл с доступом к массиву, написанный с использованием ILGenerator. Когда метод создается с этим точным кодом, я открываю дизассемблирование, и все в порядке, без проверки границ массива.

Но когда я сначала помещаю экземпляр другого класса в стек оценки, а затем запускаю цикл, он выполняет проверку границ массива. Я бегу на выпуске.

Есть идеи почему? Я уже читал сообщение в блоге о проверках, связанных с массивами: http://blogs.msdn.com/b/clrcodegeneration/archive/2009/08/13/array-bounds-check-elimination-in-the-clr.aspx

        // Uncomment this to enable bound checks, type of arg0 is some my class
        //il.Emit(OpCodes.Ldarg_0);

        var startLbl = il.DefineLabel();
        var testLbl = il.DefineLabel();
        var index = il.DeclareLocal(typeof(Int32));
        var arr = il.DeclareLocal(typeof(Int32).MakeArrayType());

        // arr = new int[4];
        il.Emit(OpCodes.Ldc_I4_4);
        il.Emit(OpCodes.Newarr, typeof(Int32));
        il.Emit(OpCodes.Stloc, arr);

        // Index = 0
        il.Emit(OpCodes.Ldc_I4_0); // Push index
        il.Emit(OpCodes.Stloc, index); // Pop index, store

        il.Emit(OpCodes.Br_S, testLbl); // Go to test

        // Begin for
        il.MarkLabel(startLbl);

        // Load array, index
        il.Emit(OpCodes.Ldloc, arr);
        il.Emit(OpCodes.Ldloc, index);

        // Now on stack: array, index
        // Load element
        il.Emit(OpCodes.Ldelem_I4);
        // Nothing here now, later some function call
        il.Emit(OpCodes.Pop);

        // Index++
        il.Emit(OpCodes.Ldloc, index);
        il.Emit(OpCodes.Ldc_I4_1);
        il.Emit(OpCodes.Add);
        il.Emit(OpCodes.Stloc, index);

        il.MarkLabel(testLbl);
        // Load index, count, test for end
        il.Emit(OpCodes.Ldloc, index);
        il.Emit(OpCodes.Ldloc, arr);
        il.Emit(OpCodes.Ldlen); // Push len
        il.Emit(OpCodes.Conv_I4); // Push len
        il.Emit(OpCodes.Blt_S, startLbl);
        // End for

        // Remove instance added on top
        //il.Emit(OpCodes.Pop);

Когда я генерирую код IL, лучше хранить экземпляры классов в оценочном стеке или в локальных переменных?

Например, я получаю экземпляр, просматриваю поля, для каждого поля делаю что-нибудь и потом возвращаюсь. Я только что сохранил экземпляр в стеке и назвал Emit(OpCodes.Dup) перед чтением следующего поля. Но это кажется неправильным (по крайней мере, для случая, упомянутого выше).

Любые статьи / посты в блогах о создании (эффективного / правильного) кода IL приветствуются.

2 ответа

Решение

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

Из того, что я видел в ILSpy, csc также предпочитает местные, хотя я должен признать, что когда смотрю на IL, а не декомпилирую в C#, это в основном отладочный код. Поскольку JIT, вероятно, написан с расчетом на то, что он будет в основном работать на выходе компиляторов Microsoft, неудивительно, если он не распознает циклические конструкции, которые не соответствуют тому, что испускают их компиляторы. Весьма вероятно, что дополнительная запись в стеке препятствует способности JIT распознать, что она может устранить проверку границ.

Работаете ли вы в режиме релиза с отладчиком, отсоединенным до того, как ваш метод будет подключен? А потом присоединяется потом? Я знаю, что нет необходимости делать этот шаг, но отладчик будет выдавать менее оптимальный код, если отладчик подключен.

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

Дрожание может быть очень трудным с кодом, который недопустим (например, неправильный стек, такой как unboxed T в стеке ожидается boxed T на общий код, который запускается только с object.) и особенно непроверяемый, который не является нормальным шаблоном. (например, небезопасный код, который никогда не произойдет через C# или C++/CLI). Вы должны всегда пытаться иметь 0 ошибок в peverify, если вы не ожидаете их. (например calli)

Другие вопросы по тегам