简体   繁体   English

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

[英]Why is it slower to compare a nullable value type to null on a generic method with no constraints?

I came across a very funny situation where comparing a nullable type to null inside a generic method is 234x slower than comparing an value type or a reference type. 我遇到了一个非常有趣的情况,在泛型方法中将可空类型与null进行比较比比较值类型或引用类型慢234倍。 The code is as follows: 代码如下:

static bool IsNull<T>(T instance)
{
    return instance == null;
}

The execution code is: 执行代码是:

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

The output for the code above is: 上面代码的输出是:

00:00:00.1879827 00:00:00.1879827

00:00:00.0008779 00:00:00.0008779

00:00:00.0008532 00:00:00.0008532

As you can see, comparing an nullable int to null is 234x slower than comparing an int or a string. 如您所见,将nullable int与null进行比较比比较int或字符串慢234倍。 If I add a second overload with the right constraints, the results change dramatically: 如果我使用正确的约束添加第二个重载,结果会发生显着变化:

static bool IsNull<T>(T? instance) where T : struct
{
    return instance == null;
}

Now the results are: 现在的结果是:

00:00:00.0006040 00:00:00.0006040

00:00:00.0006017 00:00:00.0006017

00:00:00.0006014 00:00:00.0006014

Why is that? 这是为什么? I didn't check the byte code because I'm not fluent on it, but even if the byte code was a little bit different, I would expect the JIT to optimize this, and it is not (I'm running with optimizations). 我没有检查字节码,因为我不熟悉它,但即使字节码有点不同,我希望JIT能够优化它,而不是(我正在运行优化) 。

Here's what you should do to investigate this. 以下是您应该采取的措施来调查此问题。

Start by rewriting the program so that it does everything twice. 首先重写程序,使其完成所有操作 Put a message box between the two iterations. 在两次迭代之间放置一个消息框。 Compile the program with optimizations on, and run the program not in the debugger . 使用优化编译程序,并运行不在调试器中的程序。 This ensures that the jitter generates the most optimal code it can. 这可确保抖动生成最佳代码。 The jitter knows when a debugger is attached and can generate worse code to make it easier to debug if it thinks that's what you're doing. 抖动知道何时附加调试器并且可以生成更糟糕的代码,以便在它认为您正在做的事情时更容易调试。

When the message box pops up, attach the debugger and then trace at the assembly code level into the three different versions of the code, if in fact there even are three different versions. 弹出消息框时,附加调试器,然后在汇编代码级别跟踪代码的三个不同版本,如果实际上甚至有三个不同的版本。 I'd be willing to bet as much as a dollar that no code whatsoever is generated for the first one, because the jitter knows that the whole thing can be optimized away to "return false", and then that return false can be inlined, and perhaps even the loop can be removed. 我愿意下注多达一美元,不会为第一个生成任何代码,因为抖动知道整个事情可以优化为“返回false”,然后返回false可以内联,甚至可以删除循环。

(In the future, you should probably consider this when writing performance tests. Remember that if you don't use the result then the jitter is free to completely optimize away everything that produces that result, as long as it has no side effect.) (将来,在编写性能测试时,您应该考虑这一点。请记住,如果您不使用结果,那么抖动可以自由地完全优化产生该结果的所有内容 ,只要它没有副作用。)

Once you can look at the assembly code you'll see what's going on. 一旦你看到汇编代码,你就会看到发生了什么。

I have not investigated this myself personally, but odds are good that what is going on is this: 我个人并没有对此进行过调查,但是现在发生的事情很可能是:

  • in the int codepath, the jitter is realizing that a boxed int is never null and turning the method into "return false" 在int codepath中,抖动意识到boxed int永远不会为null并将方法转换为“return false”

  • in the string codepath, the jitter is realizing that testing a string for nullity is equivalent to testing whether the managed pointer to the string is zero, so it is generating a single instruction that tests whether a register is zero. 在字符串代码路径中,抖动意识到测试字符串的无效等同于测试字符串的托管指针是否为零,因此它生成一条指令来测试寄存器是否为零。

  • in the int? 在int? codepath, probably the jitter is realizing that testing an int? codepath,可能是抖动实现了测试int? for nullity can be accomplished by boxing the int? 对于nullity可以通过装箱来完成吗? -- since a boxed null int is a null reference, that then reduces to the earlier problem of testing a managed pointer against zero. - 因为盒装的null int是一个空引用,然后减少到先前测试托管指针的问题。 But you take on the cost of the boxing. 但你承担了拳击的费用。

If that's the case then the jitter could be more sophisticated here and realize that testing an int? 如果是这种情况那么抖动可能会更复杂,并意识到测试int? for null can be accomplished by returning the inverse of the HasValue bool inside the int?. for null可以通过在int?中返回HasValue bool的逆来完成。

But like I said, that's just a guess. 但就像我说的那样,这只是猜测。 Generate the code yourself and see what it's doing if you're interested. 如果您感兴趣,请自己生成代码并查看它正在执行的操作。

If you compare the IL produced by the two overloads, you can see that there is boxing involved: 如果你比较两个重载产生的IL,你可以看到有拳击涉及:

The first looks like: 第一个看起来像:

.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 
}

While the second looks like: 而第二个看起来像:

.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 
}

In the second case, the compiler knows the type is a Nullable so it can optimize for that. 在第二种情况下,编译器知道类型是Nullable,因此可以对其进行优化。 In the first case, it has to handle any type, both reference and value types. 在第一种情况下,它必须处理任何类型,包括引用和值类型。 So it has to jump through some extra hoops. 所以它必须跳过一些额外的箍。

As for why int is faster than int?, I'd imagine there are some JIT optimizations involved there. 至于为什么int比int快?我想象那里有一些JIT优化。

Boxing and unboxing is happening there without you knowing it, and boxing operations are notoriously slow. 拳击和拆箱正在那里发生,你不知道它,拳击操作是出了名的慢。 It's because you are, in the background, converting nullable reference types to value types. 这是因为您在后台将可空引用类型转换为值类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如果泛型类型可为空,则返回null - Return null if generic type is nullable 返回可为空的泛型类型的方法 - Method that returns a nullable, generic type 模拟具有类型约束的泛型方法 - Mocking a generic method with type constraints 类型'string'必须是不可为空的值类型,以便在泛型类型或方法'System.Nullable <T>'中将其用作参数'T' - The type 'string' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>' 该类型必须是不可为空的值类型,以便在通用类型或方法“ System.Nullable”中将其用作参数“ T” <T> “ - The type must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>' 类型“ T1”必须是不可为空的值类型,以便在通用类型或方法“ System.Nullable”中将其用作参数“ T” <T> &#39; - The type 'T1' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>' 类型&#39;T&#39;必须是非可空值类型,以便在泛型类型或方法&#39;System.Nullable中将其用作参数&#39;T&#39; <T> “ - The type 'T' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>' 类型&#39;MyObject&#39;必须是非可空值类型才能在泛型类型或方法&#39;Nullable中将其用作参数&#39;T&#39; <T> “ - The type 'MyObject' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable<T>' 我可以创建一个采用值类型或引用类型但始终返回可为空类型的泛型方法吗 - Can I create a generic method that takes a value type or a reference type but always returns a nullable type 为了在通用类型或方法中将其用作参数“ T”,类型“字符串”必须为非空值类型 - The type 'string' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM