Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is typeA == typeB slower than typeA == typeof(TypeB)?

I've been optimising/benchmarking some code recently and came across this method:

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

This is called from a performance critical loop elsewhere, so I naturally assumed all those typeof(...) calls were adding unnecessary overhead (a micro-optimisation, I know) and could be moved to private fields within the class. (I'm aware there are better ways to refactor this code, however, I'd still like to know what's going on here.)

According to my benchmark this isn't the case at all (using 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;
    }
}

Results on my machine (Window 10 x64, .NET 4.7.2, RyuJIT, Release build):

The functions compiled down to 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

I don't know how to interpret ASM so am unable to understand the significance of what's happening here. In a nut shell, why is F1 faster?

like image 920
Sam Avatar asked Feb 25 '19 20:02

Sam


People also ask

Is it better to be Type A or B personality?

People with Type B personality tend to be more tolerant of others, are more relaxed than Type A individuals, more reflective, experience lower levels of anxiety and display a higher level of imagination and creativity.

What is the difference between Type A and Type B personalities?

Type A and Type B are two types of trait classification. Type A individuals are aggressive, ambitious, controlling, highly competitive, preoccupied with status, workaholics, hostile, and lack patience. Type B people are relaxed, less stressed, flexible, emotional and expressive, and have a laid-back attitude.

What are the characteristics of Type A and Type B personalities?

Type A personality traits, including competitiveness, time urgency, and a tendency toward workaholism, can be seen (particularly by Type A people) as beneficial for career success. 1 In contrast, Type B personalities tend to be less focused on competitiveness and more on enjoying the journey.

What are Type A personality weaknesses?

Type A personalities are typically driven, ambitious, successful, and may even live longer. But, they are also more stressed and prone to depression, anxiety, and relationship problems. Type A personalities can try to be happier by practicing more patience with themselves and others.


2 Answers

The assembly you posted shows that the comment of mjwills is, as expected, correct. As the linked article notes, the jitter can be smart about certain comparisons, and this is one of them.

Let's look at your first fragment:

mov     rcx,offset mscorlib_ni+0x729e10

rcx is the "this pointer" of a call to a member function. The "this pointer" in this case will be the address of some CLR pre-allocated object, what exactly I do not know.

call    clr!InstallCustomModule+0x2320

Now we call some member function on that object; I don't know what. The nearest public function that you have debug info for is InstallCustomModule, but plainly we are not calling InstallCustomModule here; we're calling the function that is 0x2320 bytes away from InstallCustomModule.

It would be interesting to see what the code at InstallCustomModule+0x2320 does.

Anyways, we make the call, and the return value goes in rax. Moving on:

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

This looks like it is fetching the value of a out of this and comparing it to whatever the function returned.

The rest of the code is just perfectly ordinary: moving the bool result of the comparison into the return register.

In short, the first fragment is equivalent to:

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

Obviously an educated guess here is that the constant object and the unknown function are special-purpose helpers that rapidly fetch commonly-used type objects like typeof(int).

A second educated guess is that the jitter is deciding for itself that the pattern "compare a field of type Type to a typeof(something)" can best be made as a direct reference comparison between objects.

And now you can see for yourself what the second fragment does. It is just:

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

All it does is call a helper method that compares two types for value equality. Remember, the CLR does not guarantee reference equality for all equivalent type objects.

Now it should be clear why the first fragment is faster. The jitter knows hugely more about the first fragment. It knows, for instance, that typeof(int) will always return the same reference, and so you can do a cheap reference comparison. It knows that typeof(int) is never null. It knows the exact type of typeof(int) -- remember, Type is not sealed; you can make your own Type objects.

In the second fragment, the jitter knows nothing other than it has two operands of type Type. It doesn't know their runtime types, it doesn't know their nullity; for all it knows, you subclassed Type yourself and made up two instances that are reference-unequal but value-equal. It has to fall back to the most conservative position and call a helper method that starts going down the list: are they both null? Is one of the null and the other non-null? are they reference equal? And so on.

It looks like lacking that knowledge is costing you the enormous penalty of... half a nanosecond. I wouldn't worry about it.

like image 156
Eric Lippert Avatar answered Sep 30 '22 00:09

Eric Lippert


If you are curious, you can also look at the logic the jit uses, see gtFoldTypeCompare.

There are a whole bunch of things the jit can do to simplify or even eliminate type comparisons. They all require knowing something about the creation of the types being compared.

like image 39
Andy Ayers Avatar answered Sep 30 '22 00:09

Andy Ayers