Почему typeA == typeB медленнее, чем typeA == typeof (TypeB)? - PullRequest
0 голосов
/ 25 февраля 2019

Недавно я оптимизировал / провел сравнительный анализ кода и наткнулся на этот метод:

public void SomeMethod(Type messageType)
{
    if (messageType == typeof(BroadcastMessage))
    {
        // ...
    }
    else if (messageType == typeof(DirectMessage))
    {
        // ...
    }
    else if (messageType == typeof(ClientListRequest))
    {
        // ...
    }
}

В другом месте он вызывается из цикла, критичного к производительности, поэтому я, естественно, предположил, что все эти вызовы typeof(...) были добавленыненужные накладные расходы (микрооптимизация, я знаю) и могут быть перемещены в частные поля в классе.(Я знаю, что есть более эффективные способы реорганизации этого кода, однако я все же хотел бы знать, что здесь происходит.)

Согласно моему тесту, это совсем не так (использование BenchmarkDotNet ).

[DisassemblyDiagnoser(printAsm: true, printSource: true)]
[RyuJitX64Job]
public class Tests
{
    private Type a = typeof(string);
    private Type b = typeof(int);

    [Benchmark]
    public bool F1()
    {
        return a == typeof(int);
    }

    [Benchmark]
    public bool F2()
    {
        return a == b;
    }
}

Результаты на моем компьютере (Windows 10 x64, .NET 4.7.2, RyuJIT, сборка выпуска):

Функции, скомпилированные до ASM:

F1

mov     rcx,offset mscorlib_ni+0x729e10
call    clr!InstallCustomModule+0x2320
mov     rcx,qword ptr [rsp+30h]
cmp     qword ptr [rcx+8],rax
sete    al
movzx   eax,al

F2

mov     qword ptr [rsp+30h],rcx
mov     rcx,qword ptr [rcx+8]
mov     rdx,qword ptr [rsp+30h]
mov     rdx,qword ptr [rdx+10h]
call    System.Type.op_Equality(System.Type, System.Type)
movzx   eax,al

Я не знаю, как интерпретироватьASM не может понять значение того, что здесь происходит.В скорлупе ореха почему F1 быстрее?

Ответы [ 2 ]

0 голосов
/ 07 марта 2019

Если вам интересно, вы также можете взглянуть на логику, которую использует jit, см. gtFoldTypeCompare .

Существует целый ряд вещей, которые jit может сделать, чтобы упростить или даже устранитьсравнения типов.Все они требуют знания кое-чего о создании сравниваемых типов.

0 голосов
/ 26 февраля 2019

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

Давайте рассмотрим ваш первый фрагмент:

mov     rcx,offset mscorlib_ni+0x729e10

rcx - это указатель this«вызова функции-члена.«Указатель this» в этом случае будет адресом предварительно выделенного объекта CLR, чего я точно не знаю.

call    clr!InstallCustomModule+0x2320

Теперь мы вызываем некоторую функцию-член для этого объекта;Я не знаю что. ближайшая публичная функция, для которой у вас есть отладочная информация, - это InstallCustomModule, но, очевидно, мы здесь не вызываем InstallCustomModule;мы вызываем функцию, которая находится в 0x2320 байтах от InstallCustomModule.

Было бы интересно посмотреть, что делает код в InstallCustomModule + 0x2320.

В любом случае, мы делаем вызов, ивозвращаемое значение идет в rax.Двигаемся дальше:

mov     rcx,qword ptr [rsp+30h]
cmp     qword ptr [rcx+8],rax

Похоже, что он извлекает значение a из this и сравнивает его с тем, что вернула функция.

Остальная часть кодапросто совершенно обычное: перемещение результата bool сравнения в регистр возврата.

Короче говоря, первый фрагмент эквивалентен:

return ReferenceEquals(SomeConstantObject.SomeUnknownFunction(), this.a);

Очевидно, что здесь прослеживается догадка о том, что константаобъект и неизвестная функция являются специальными помощниками, которые быстро выбирают объекты часто используемых типов, такие как typeof (int).

Второе обоснованное предположение состоит в том, что джиттер сам для себя решает, что шаблон "сравнивает полеtype Type to typeof (что-то) "лучше всего сделать как прямое сравнение ссылок между объектами.

И теперь вы можете сами убедиться, что делает второй фрагмент.Это просто:

return Type.op_Equality(this.a, this.b);

Все, что он делает, это вызывает вспомогательный метод, который сравнивает два типа для равенства значений.Помните, CLR не гарантирует равенство ссылок для всех объектов эквивалентного типа .

Теперь должно быть понятно, почему первый фрагмент быстрее. Джиттер знает гораздо больше о первом фрагменте .Он знает, например, что typeof (int) всегда будет возвращать одну и ту же ссылку, и поэтому вы можете сделать дешевое сравнение ссылок.Он знает, что typeof (int) никогда не бывает нулевым.Он знает точный тип typeof (int) - помните, Type не запечатан;Вы можете создавать свои собственные Type объекты.

Во втором фрагменте джиттер не знает ничего, кроме того, что у него есть два операнда типа Type.Он не знает их типов во время выполнения, он не знает их недействительности;Насколько он знает, вы сами подклассифицировали Type и создали два экземпляра, которые являются неравными по ссылкам, но равными по стоимости.Он должен вернуться к наиболее консервативной позиции и вызвать вспомогательный метод, который начинает идти вниз по списку: оба они равны нулю?Является ли одно из нулевого, а другой ненулевым?равны ли они?И так далее.

Похоже, отсутствие знаний обходится вам в огромные штрафы ... полсекунды.Я бы не волновался об этом.

...