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 毫秒

我可以理解这个结果,因为将可空结构转换为该结构object的盒装类型。但是为什么不强制struct s转换object为进行空比较(如在第一种方法中)导致相同的性能?编译器是否正在优化调用以false始终返回,因为结构不能为空?

4

2 回答 2

8

是的,编译器正在优化它。

它知道结构永远不能为空,因此将其转换为对象的结果永远不能为空 - 所以它只会b在第一个示例中设置为 false。事实上,如果你使用Resharper,它会警告你表达式总是错误的。

当然,第二个可以为空的可以为空,因此它必须进行检查。

(您也可以使用Reflector检查编译器生成的 IL 代码来验证这一点。)

原始测试代码不好,因为编译器知道可空结构将始终为空,因此也会优化该循环。不仅如此,而且在发布版本中,编译器意识到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

事实上,两个循环在编译时都会有一个空的主体!

要使第二个循环正常运行,您必须删除(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

第二个循环

.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 回答