Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/258.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 为什么在没有约束的泛型方法中将可空值类型与null进行比较会比较慢?_C#_Performance_Compiler Construction_Nullable_Jit - Fatal编程技术网

C# 为什么在没有约束的泛型方法中将可空值类型与null进行比较会比较慢?

C# 为什么在没有约束的泛型方法中将可空值类型与null进行比较会比较慢?,c#,performance,compiler-construction,nullable,jit,C#,Performance,Compiler Construction,Nullable,Jit,我遇到了一个非常有趣的情况,在泛型方法中比较可为null的类型和null比比较值类型或引用类型慢234倍。代码如下: static bool IsNull<T>(T instance) { return instance == null; } 静态bool为空(T实例) { 返回实例==null; } 执行代码为: int? a = 0; string b = "A"; int c = 0; var watch = Stopwatch.StartNew(); for

我遇到了一个非常有趣的情况,在泛型方法中比较可为null的类型和null比比较值类型或引用类型慢234倍。代码如下:

static bool IsNull<T>(T instance)
{
    return instance == null;
}
静态bool为空(T实例)
{
返回实例==null;
}
执行代码为:

int? a = 0;
string b = "A";
int c = 0;

var watch = Stopwatch.StartNew();

for (int i = 0; i < 1000000; i++)
{
    var r1 = IsNull(a);
}

Console.WriteLine(watch.Elapsed.ToString());

watch.Restart();

for (int i = 0; i < 1000000; i++)
{
    var r2 = IsNull(b);
}

Console.WriteLine(watch.Elapsed.ToString());

watch.Restart();

for (int i = 0; i < 1000000; i++)
{
    var r3 = IsNull(c);
}

watch.Stop();

Console.WriteLine(watch.Elapsed.ToString());
Console.ReadKey();
int?a=0;
字符串b=“A”;
int c=0;
var watch=Stopwatch.StartNew();
对于(int i=0;i<1000000;i++)
{
var r1=IsNull(a);
}
Console.WriteLine(watch.appeased.ToString());
watch.Restart();
对于(int i=0;i<1000000;i++)
{
var r2=IsNull(b);
}
Console.WriteLine(watch.appeased.ToString());
watch.Restart();
对于(int i=0;i<1000000;i++)
{
var r3=IsNull(c);
}
看,停;
Console.WriteLine(watch.appeased.ToString());
Console.ReadKey();
上述代码的输出为:

00:00:00.1879827

00:00:00.0008779

00:00:00.0008532

如您所见,将可为null的int与null进行比较比比较int或字符串慢234倍。如果使用正确的约束添加第二个重载,结果会发生显著变化:

static bool IsNull<T>(T? instance) where T : struct
{
    return instance == null;
}
静态bool为null(T?实例),其中T:struct
{
返回实例==null;
}
现在的结果是:

00:00:00.0006040

00:00:00.0006017

00:00:00.0006014


为什么呢?我没有检查字节码,因为我对字节码不太熟练,但即使字节码有点不同,我也希望JIT能对此进行优化,但事实并非如此(我正在进行优化)。

在你不知道的情况下,装箱和解装箱正在进行,装箱操作的速度非常慢。这是因为在后台,您正在将可为空的引用类型转换为值类型。

如果比较两个重载生成的IL,您可以看到其中涉及到装箱:

第一个看起来像:

.method private hidebysig static bool IsNull<T>(!!T instance) cil managed
{
    .maxstack 2
    .locals init (
        [0] bool CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: box !!T
    L_0007: ldnull 
    L_0008: ceq 
    L_000a: stloc.0 
    L_000b: br.s L_000d
    L_000d: ldloc.0 
    L_000e: ret 
}
.method private hidebysig static bool IsNull<valuetype ([mscorlib]System.ValueType) .ctor T>(valuetype [mscorlib]System.Nullable`1<!!T> instance) cil managed
{
    .maxstack 2
    .locals init (
        [0] bool CS$1$0000)
    L_0000: nop 
    L_0001: ldarga.s instance
    L_0003: call instance bool [mscorlib]System.Nullable`1<!!T>::get_HasValue()
    L_0008: ldc.i4.0 
    L_0009: ceq 
    L_000b: stloc.0 
    L_000c: br.s L_000e
    L_000e: ldloc.0 
    L_000f: ret 
}
.method private hidebysing static bool为空(!!T instance)cil托管
{
.maxstack 2
.init(
[0]bool CS$1$0000)
L_0000:没有
L_0001:ldarg.0
L_0002:box!!T
L_0007:ldnull
L_0008:ceq
L_000a:stloc.0
L_000b:br.s L_000d
L_000d:ldloc.0
L_000e:ret
}
第二个看起来像:

.method private hidebysig static bool IsNull<T>(!!T instance) cil managed
{
    .maxstack 2
    .locals init (
        [0] bool CS$1$0000)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: box !!T
    L_0007: ldnull 
    L_0008: ceq 
    L_000a: stloc.0 
    L_000b: br.s L_000d
    L_000d: ldloc.0 
    L_000e: ret 
}
.method private hidebysig static bool IsNull<valuetype ([mscorlib]System.ValueType) .ctor T>(valuetype [mscorlib]System.Nullable`1<!!T> instance) cil managed
{
    .maxstack 2
    .locals init (
        [0] bool CS$1$0000)
    L_0000: nop 
    L_0001: ldarga.s instance
    L_0003: call instance bool [mscorlib]System.Nullable`1<!!T>::get_HasValue()
    L_0008: ldc.i4.0 
    L_0009: ceq 
    L_000b: stloc.0 
    L_000c: br.s L_000e
    L_000e: ldloc.0 
    L_000f: ret 
}
.method private hidebysing static bool IsNull(valuetype[mscorlib]System.Nullable`1实例)cil托管
{
.maxstack 2
.init(
[0]bool CS$1$0000)
L_0000:没有
L_0001:ldarga.s实例
L_0003:调用实例bool[mscorlib]System.Nullable`1::get_HasValue()
L_0008:ldc.i4.0
L_0009:ceq
L_000b:stloc.0
L_000c:br.s L_000e
L_000e:ldloc.0
L_000f:ret
}
在第二种情况下,编译器知道该类型是可空的,因此可以为此进行优化。在第一种情况下,它必须处理任何类型,包括引用类型和值类型。因此,它必须通过一些额外的环


至于为什么int比int快,我想这里涉及到一些JIT优化。

以下是您应该做的事情来研究这一点

从重写程序开始,让它每件事都做两次。在两次迭代之间放置一个消息框。在启用优化的情况下编译程序,并在调试器中运行程序。这确保了抖动可以生成最佳的代码。jitter知道何时连接了调试器,如果它认为这就是您正在做的,它会生成更糟糕的代码,从而使调试更容易

当消息框弹出时,附加调试器,然后在汇编代码级别跟踪代码的三个不同版本(如果实际上有三个不同版本)。我愿意赌一美元,第一个循环不会生成任何代码,因为抖动知道整个过程可以优化为“return false”,然后return false可以内联,甚至可以删除循环

(在将来,在编写性能测试时,你应该考虑这一点。记住,如果你不使用结果,那么抖动就可以完全地去优化产生结果的所有东西,只要它没有副作用。)< /P> 一旦您可以查看汇编代码,您将看到发生了什么

我本人并未对此进行调查,但很有可能发生的情况如下:

  • 在int代码路径中,jitter实现了装箱的int永远不会为null,并将该方法转换为“returnfalse”

  • 在字符串代码路径中,jitter实现了测试字符串的空性等同于测试指向该字符串的托管指针是否为零,因此它生成了一条测试寄存器是否为零的指令

  • 在整数里?代码路径,可能是抖动意识到测试int?对于空值,可以通过装箱整型来实现--由于装箱的null int是一个null引用,因此这就简化为前面针对零测试托管指针的问题。但你要承担拳击的费用

如果是这样的话,那么抖动可能会更复杂,并意识到测试int?for null可以通过返回int?内HasValue bool的倒数来实现


但就像我说的,这只是一个猜测。如果你感兴趣,自己生成代码,看看它在做什么。

有了这样的结果——在最坏的情况下,100万次迭代的时间少于0.2秒,这有关系吗?是的,如果你以每秒100万次的速度这样做。我知道。别忘了,您正在测量一百万次迭代的成本和第一次调用时对代码进行jitting的成本之和。如果代码真的很便宜,就像这段代码一样,只发生一次的jit成本实际上可以控制平均成本。在同一个程序中运行两次测试可能会很有趣,这样第二次,代码是“热的”