我在进行基准测试时遇到了这个问题。
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
总是因为结构不能为空?
答案 0 :(得分:8)
是的,编译器正在优化它。
它知道struct永远不能为null,因此将它转换为对象的结果永远不会为null - 因此它只会在第一个示例中将b
设置为false。实际上,如果您使用 Resharper ,它会警告您表达式始终为false。
对于第二个当然,可以为空可以为空,因此必须进行检查。
(您也可以使用Reflector
检查编译器生成的IL代码以验证这一点。)
原始测试代码不好,因为编译器知道可空结构将始终为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 {}
}
答案 1 :(得分: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();
}
}
IL:
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