1

In another questions in SO (such as this one and this other one) it has been stated that the is operator has worse performance compared to other options to check the type of an object.

I wanted to test this so I write some code (TIO link so you can run the tests and play with the code) and at first the results seemed to indicate that is has indeed worse performance. Following are the relevant parts of the tests:

// is operator
var a = 0;
for (int i = 0; i < ITERATIONS; i++)
{
    if (test is B) a += 1;
    if (test is C) a += 2;
    if (test is D) a += 3;
    if (test is E) a += 4;
}

// typeof operator
var a = 0;
var t = test.GetType();
for (int i = 0; i < ITERATIONS; i++)
{
    if (t == typeof(B)) a += 1;
    if (t == typeof(C)) a += 2;
    if (t == typeof(D)) a += 3;
    if (t == typeof(E)) a += 4;
}

// TypeHandle
var a = 0;
var t = Type.GetTypeHandle(test);
for (int i = 0; i < ITERATIONS; i++)
{
    if (t.Equals(typeof(B).TypeHandle)) a += 1;
    if (t.Equals(typeof(C).TypeHandle)) a += 2;
    if (t.Equals(typeof(D).TypeHandle)) a += 3;
    if (t.Equals(typeof(E).TypeHandle)) a += 4;
}

When setting the number of iterations to 1000 and 10000 the results were as expected, but as I increased the number of iterations the results were inverted and now the is performance was better. Following is the table you can get when playing with the number of iterations (times are in milliseconds):

ITERATIONS    1000      10000     100000    1000000   10000000
---------------------------------------------------------------
is            1.8608    2.1862    4.7757    28.1402   234.7027
typeof        0.4702    0.8296    3.9109    33.0174   328.5222
TypeHandle    0.5815    1.0586    6.1271    53.4649   536.3979

I get the same results (with different numbers, but the same result nonetheless) in my local machine: the higher the number of iterations, the faster is the performance of is.

So what is happening here? Is this another consequence of branch prediction?

Charlie
  • 624
  • 1
  • 8
  • 22
  • I would start off by using a more thorough approach to benchmarking. I'd suggest porting your benchmarks to https://benchmarkdotnet.org (BenchmarkDotNet) – Jon Skeet Sep 27 '18 at 11:06
  • a couple ms is short enough to worry about startup effects, and ramp up to max clock speed happening part-way through the total run of the program. If reversing the order you test things in makes a big difference, your microbenchmark is broken, so try that. Linear scaling with number of iterations is another thing you need to look for as a sanity check, if you're trying to measure the fully-warmed-up case. (But yeah, it's hard to replicate the context of a large program in a microbenchmark, where cache / JIT / whatever might be hot but branch prediction isn't.) – Peter Cordes Sep 27 '18 at 11:18
  • 1
    @PeterCordes interesting. I've tried to test the `is` operator case at the end instead of being the first one, and now that case is always the fastest. I'll try to use the benchmarking tool that Jon Skeet suggested. – Charlie Sep 27 '18 at 11:22
  • @JonSkeet thank you for your suggestion! I've executed the benchmark using BechmarkDotNet and now the results show that the `is` operator is slightly more expensive than the `typeof` operator regardless of the number of iterations. :-) – Charlie Sep 27 '18 at 11:48
  • It's not fair comparison I think. For `typeof` and `TypeHandle` part of work is done outside of the loop, I mean when you determine the object type, while inside you only compare the references. The `is` test however does this work on each iteration. – Dmytro Mukalov Sep 27 '18 at 12:32

0 Answers0