Какова цель кода операции castclass в CLR? - PullRequest
0 голосов
/ 12 декабря 2018

Я наткнулся на код операции castclass, который определен как Standard ECMA - 335, III.4.3.Я написал несколько примеров использования callvirt кода операции с кастингом и без.Оказывается, что castclass код операции имеет большое влияние на производительность.

Для тестирования я использовал следующую «грубую» (с точки зрения неточности времени выполнения методов) программу (скомпилированную в Release mode с помощью msvc 2015):

public class Program
{
    public interface IShow {  string show(); }
    public class ObjectWithShow : IShow 
    { 
        public string show() => "Hello, that's the show method."; 
    }
    // Such functions remains unchanged
    public static string showWithCast(object o) => ((IShow)o).show();
    public static string show(IShow o) => o.show();
    // Such function will be patched later
    public static string showWithoutCast(object o) => ((IShow)o).show();

    public static void Main()
    {
        int N = 10000000;
        var show_object = new ObjectWithShow();
        {

           var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < N; ++i)
            {
                showWithCast(show_object);
            }
            watch.Stop();
            Console.WriteLine($"With cast {watch.ElapsedMilliseconds}");
        }
        {

            var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < N; ++i)
            {
                showWithoutCast(show_object);
            }
            watch.Stop();
            Console.WriteLine($"Without cast {watch.ElapsedMilliseconds}");
        }
        {

            var watch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < N; ++i)
            {
                show(show_object);
            }
            watch.Stop();
            Console.WriteLine($"Without cast {watch.ElapsedMilliseconds}");
        }
    }
}

ЗдесьIL коды show/showWitCast функций:

.method public hidebysig static string show (class IShow o) cil managed 
{
    .maxstack 8
    IL_0000: ldarg.0
    IL_0001: callvirt instance string IShow::show()
    IL_0006: ret
} // end of method Program::show


.method public hidebysig static string showWithCast (object o) cil managed 
{
    .maxstack 8
    IL_0000: ldarg.0
    IL_0001: castclass IShow
    IL_0006: callvirt instance string IShow::show()
    IL_000b: ret
} // end of method Program::showWithCast

Вот код для showWithoutCast (ПРИМЕЧАНИЕ: я пропатчил его, удалив castclass IShow в редакторе IL. Исходная версия была такой же, как showWithCast)

.method public hidebysig static string showWithoutCast (object o) cil managed 
{

    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: callvirt instance string IShow::show()
    IL_0006: ret
} // end of method Program::showWithoutCast

Результаты выполнения (i7-3370 CPU @ 3,40 ГГц, 8 ГБ ОЗУ) показывают следующий результат:

С приведением 46

Безприведение 24

без преобразования 23

Оказывается, что callvirt для объекта без castclass показывает почти такой же результат, как мы использовали вместо этого экземпляр интерфейса.Итак, какова цель castclass?Я предполагаю, что c# compiler испускает такой код, чтобы гарантировать, что код операции callvirt не будет использоваться для некорректных типов (потому что он не может выполнять такие проверки во время компиляции).Итак, следующий вопрос - является ли это последовательным CIL кодом, где я намеренно исключаю использование castclass в местах, где я ГАРАНТИРУЮ, что метод будет использоваться только для типов, которые реализуют IShow?

PS Конечно, вы можете спросить, может быть, тогда следует использовать метод show?Но есть случаи, когда такую ​​функцию нельзя использовать.Если быть кратким, я динамически генерирую код и хочу реализовать универсальный контейнер (он наследует IShow), но его универсальный параметр может дополнительно реализовывать интерфейс IShow.Если универсальный параметр не реализует интерфейс (например, это int), тогда я гарантирую, что метод show контейнера не будет использоваться.

1 Ответ

0 голосов
/ 13 декабря 2018

Все инструкции callvirt instance string IShow::show вызывают одну и ту же заглушку, которая переходит на заглушку поиска , связанную с методом интерфейса.Заглушка поиска разрешит вызываемый метод в зависимости от типа объекта, который получает вызов.В этом случае объект реализует IShow, так что, как видите, все работает хорошо.Однако, если вы передадите объект, который не реализует IShow, заглушка поиска не найдет IShow::show в таблице методов объекта, и, таким образом, будет сгенерировано исключение типа EntryPointNotFoundException.

Стек оценкивиртуальной машины IL содержит объект типа object во время выполнения инструкции callvirt.Целевой метод - IShow::show().В соответствии с разделом III.4.2 спецификации CLI тип object должен быть назначаемым для верификатора на IShow, чтобы код IL можно было проверять.castclass делает код проверяемым.В этом случае, поскольку код является полностью доверенным, проверка автоматически пропускается, и поэтому исключение проверки не выдается, а метод получает JIT, скомпилированный и выполненный.

Технически, в этом случае showWithoutCast не содержит никакихИнструкция IL, которая должна вызывать исключение типа EntryPointNotFoundException в соответствии со спецификацией.Тем не менее, поскольку код не поддается проверке, в разделе II.3 стандарта говорится, что в случае сбоя проверки поведение не указано .То есть реализация не обязана документировать, какое поведение происходит.С другой стороны, поведение проверяемого кода указано , а castclass делает проверку успешной.

Обратите внимание, что когда вы создаете код IL локально на своей машине и запускаете его, он автоматически будет считаться полностью доверенным.Поэтому JIT-компилятор не будет проверять какой-либо метод.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...