Ответ 1
Размещенная вами сборка показывает, что комментарий mjwills, как и ожидалось, правильный. Как отмечает связанная статья, джиттер может быть осторожен в определенных сравнениях, и это одно из них.
Давайте посмотрим на ваш первый фрагмент:
mov rcx,offset mscorlib_ni+0x729e10
rcx - это указатель 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 с typeof (что-то)" лучше всего можно сделать в виде прямого эталонного сравнения между объектами.
И теперь вы можете сами убедиться, что делает второй фрагмент. Просто:
return Type.op_Equality(this.a, this.b);
Все, что он делает, - это вызывает вспомогательный метод, который сравнивает два типа на равенство значений. Помните, что CLR не гарантирует равенство ссылок для всех объектов эквивалентного типа.
Теперь должно быть понятно, почему первый фрагмент быстрее. Джиттер знает гораздо больше о первом фрагменте. Он знает, например, что typeof (int) всегда будет возвращать одну и ту же ссылку, и поэтому вы можете сделать дешевое сравнение ссылок. Он знает, что typeof (int) никогда не бывает нулевым. Он знает точный тип typeof (int) - помните, Type
не запечатан; Вы можете создавать свои собственные объекты Type
.
Во втором фрагменте джиттер не знает ничего, кроме того, что у него есть два операнда типа Type
. Он не знает их типов во время выполнения, он не знает их недействительности; Насколько он знает, вы сами подклассифицировали Type
и создали два экземпляра, которые являются неравнозначными, но равными по значению. Он должен вернуться к наиболее консервативной позиции и вызвать вспомогательный метод, который начинает идти вниз по списку: оба они равны нулю? Является ли одно из нулевого, а другой ненулевым? равны ли они? И так далее.
Похоже, отсутствие знаний обходится вам в огромные штрафы... полсекунды. Я бы не беспокоился об этом.