Проверка границ массива в 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
)