7

ベンチマークを行っているときにこれに出くわしました。

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

デバッグ: 200 ミリ秒

リリース: 5 ミリ秒

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

デバッグ: 800 ミリ秒

リリース: 800 ミリ秒

objectnull 許容構造体をキャストすると、その構造体のボックス化された型が得られるため、この結果を理解できます。しかし、(最初の方法のように) null 比較を行うために にキャストstruct sobjectても、同じパフォーマンスが得られないのはなぜでしょうか? false構造体をnullにすることはできないため、コンパイラが常に返す呼び出しを最適化しているということですか?

4

2 に答える 2

8

はい、コンパイラはそれを最適化しています。

構造体が null にならないことがわかっているため、構造体をオブジェクトにキャストした結果が null になることはありません。そのためb、最初のサンプルで false に設定されます。実際、Resharperを使用すると、式が常に false であることが警告されます。

もちろん、2番目の場合、nullableはnullになる可能性があるため、チェックを行う必要があります。

Reflector(これを確認するために、コンパイラによって生成された IL コードを検査するために使用することもできます。)

元のテスト コードは適切ではありません。コンパイラは、null 許容構造体が常に null になることを認識しているため、そのループも最適化して排除するからです。それだけでなく、リリース ビルドでは、コンパイラはそれbが使用されていないことを認識し、ループ全体を最適化します。

それを防ぎ、より現実的なコードで何が起こるかを示すには、次のようにテストします。

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 {}
}
于 2013-04-16T11:10:25.637 に答える
3

実際、コンパイル時に両方のループの本体が空になります。

2 番目のループを動作させるには、(object)キャストを削除する必要があります

コードをコンパイルすると、次のようになります。

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();
    }
}

イル:

MyStruct (提供しなかったため空)

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

} // end of class ConsoleApplication1.MyStruct

あなたの例の最初のループ

.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

2番目のループ

.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
于 2013-04-16T11:19:58.767 に答える