سؤال

I came across this while doing some benchmarking.

bool b;
MyStruct s;
for (int i = 0; i < 10000000; i++)
{
    b = (object)s == null;
}

Debug: 200 ms

Release: 5 ms

bool b;
MyStruct? s = null;
for (int i = 0; i < 10000000; i++)
{
    b = (object)s == null;
}

Debug: 800 ms

Release: 800 ms

I can understand this result since casting the nullable struct to object gives me a boxed type of that struct. But why isn't casting struct s to object for doing null comparison (as in the first method) result in the same performance? Is it that compiler is optimizing the call to return false always as a struct can't be null?

هل كانت مفيدة؟

المحلول

Yes, the compiler is optimising it.

It knows that a struct can never be null, so the result of casting it to an object can never be null - so it will just set b to false in the first sample. In fact, if you use Resharper, it will warn you that the expression is always false.

For the second of course, a nullable can be null so it has to do the check.

(You can also use Reflector to inspect the compiler-generated IL code to verify this.)

The original test code is not good because the compiler knows that the nullable struct will always be null and will therefore also optimize away that loop. Not only that, but in a release build the compiler realises that b is not used and optimizes away the entire loop.

To prevent that, and to show what would happen in more realistic code, test it like so:

using System;
using System.Diagnostics;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            bool b = true;
            MyStruct? s1 = getNullableStruct();
            Stopwatch sw = Stopwatch.StartNew();

            for (int i = 0; i < 10000000; i++)
            {
                b &= (object)s1 == null; // Note: Redundant cast to object.
            }

            Console.WriteLine(sw.Elapsed);

            MyStruct s2 = getStruct();
            sw.Restart();

            for (int i = 0; i < 10000000; i++)
            {
                b &= (object)s2 == null;
            }

            Console.WriteLine(sw.Elapsed);
        }

        private static MyStruct? getNullableStruct()
        {
            return null;
        }

        private static MyStruct getStruct()
        {
            return new MyStruct();
        }
    }

    public struct MyStruct {}
}

نصائح أخرى

in fact both loop will have an empty body when compiled!

to make the second loop behave, you will have to remove the (object) casting

this is what it look like when I compile your code,

public struct MyStruct
{ 
}

class Program
{
    static void Main(string[] args)
    {
        test1();
        test2();
    }

    public static void test1()
    {
        Stopwatch sw = new Stopwatch();
        bool b;
        MyStruct s;
        for (int i = 0; i < 100000000; i++)
        {
            b = (object)s == null;
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
        Console.ReadLine();
    }

    public static void test2()
    {
        Stopwatch sw = new Stopwatch();
        bool b;
        MyStruct? s = null;
        for (int i = 0; i < 100000000; i++)
        {
            b = (object)s == null;
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
        Console.ReadLine();
    }
}

IL:

the MyStruct (empty since you didn't provide any)

.class public sequential ansi sealed beforefieldinit ConsoleApplication1.MyStruct
extends [mscorlib]System.ValueType
{
    .pack 0
    .size 1

} // end of class ConsoleApplication1.MyStruct

the first loop in your example

.method public hidebysig static 
void test1 () cil managed 
{
// Method begins at RVA 0x2054
// Code size 17 (0x11)
.maxstack 2
.locals init (
    [0] valuetype ConsoleApplication1.MyStruct s,
    [1] int32 i
)

IL_0000: ldc.i4.0
IL_0001: stloc.1
IL_0002: br.s IL_0008
// loop start (head: IL_0008)
    IL_0004: ldloc.1
    IL_0005: ldc.i4.1
    IL_0006: add
    IL_0007: stloc.1

    IL_0008: ldloc.1
    IL_0009: ldc.i4 100000000
    IL_000e: blt.s IL_0004
// end loop

IL_0010: ret
} // end of method Program::test1

the second loop

.method public hidebysig static 
void test2 () cil managed 
{
// Method begins at RVA 0x2074
// Code size 25 (0x19)
.maxstack 2
.locals init (
    [0] valuetype [mscorlib]System.Nullable`1<valuetype ConsoleApplication1.MyStruct> s,
    [1] int32 i
)

IL_0000: ldloca.s s
IL_0002: initobj valuetype [mscorlib]System.Nullable`1<valuetype ConsoleApplication1.MyStruct>
IL_0008: ldc.i4.0
IL_0009: stloc.1
IL_000a: br.s IL_0010
// loop start (head: IL_0010)
    IL_000c: ldloc.1
    IL_000d: ldc.i4.1
    IL_000e: add
    IL_000f: stloc.1

    IL_0010: ldloc.1
    IL_0011: ldc.i4 100000000
    IL_0016: blt.s IL_000c
// end loop

IL_0018: ret
} // end of method Program::test2
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top