带有“as"的性能惊喜和可空类型 [英] Performance surprise with "as" and nullable types

查看:26
本文介绍了带有“as"的性能惊喜和可空类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我只是在修改 C# 中处理可空类型的第 4 章,我正在添加一个关于使用as"运算符的部分,它允许您编写:

I'm just revising chapter 4 of C# in Depth which deals with nullable types, and I'm adding a section about using the "as" operator, which allows you to write:

object o = ...;
int? x = o as int?;
if (x.HasValue)
{
    ... // Use x.Value in here
}

我认为这真的很巧妙,它可以提高 C# 1 等效项的性能,使用is"后跟强制转换 - 毕竟,这样我们只需要请求一次动态类型检查,然后简单的值检查.

I thought this was really neat, and that it could improve performance over the C# 1 equivalent, using "is" followed by a cast - after all, this way we only need to ask for dynamic type checking once, and then a simple value check.

然而,情况似乎并非如此.我在下面包含了一个示例测试应用程序,它基本上对对象数组中的所有整数求和 - 但该数组包含许多空引用和字符串引用以及装箱整数.基准测试测量您必须在 C# 1 中使用的代码、使用as"运算符的代码,以及仅用于踢 LINQ 解决方案的代码.令我惊讶的是,在这种情况下,C# 1 代码的速度提高了 20 倍 - 甚至 LINQ 代码(考虑到所涉及的迭代器,我原以为它会更慢)也比as"代码快.

This appears not to be the case, however. I've included a sample test app below, which basically sums all the integers within an object array - but the array contains a lot of null references and string references as well as boxed integers. The benchmark measures the code you'd have to use in C# 1, the code using the "as" operator, and just for kicks a LINQ solution. To my astonishment, the C# 1 code is 20 times faster in this case - and even the LINQ code (which I'd have expected to be slower, given the iterators involved) beats the "as" code.

isinst 对于可空类型的 .NET 实现真的很慢吗?是额外的 unbox.any 导致了问题吗?对此还有其他解释吗?目前,我觉得我将不得不警告不要在性能敏感的情况下使用它......

Is the .NET implementation of isinst for nullable types just really slow? Is it the additional unbox.any that causes the problem? Is there another explanation for this? At the moment it feels like I'm going to have to include a warning against using this in performance sensitive situations...

结果:

演员表:10000000:121
如:10000000:2211
LINQ:10000000:2143

Cast: 10000000 : 121
As: 10000000 : 2211
LINQ: 10000000 : 2143

代码:

using System;
using System.Diagnostics;
using System.Linq;

class Test
{
    const int Size = 30000000;

    static void Main()
    {
        object[] values = new object[Size];
        for (int i = 0; i < Size - 2; i += 3)
        {
            values[i] = null;
            values[i+1] = "";
            values[i+2] = 1;
        }

        FindSumWithCast(values);
        FindSumWithAs(values);
        FindSumWithLinq(values);
    }

    static void FindSumWithCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            if (o is int)
            {
                int x = (int) o;
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Cast: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;
            if (x.HasValue)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }

    static void FindSumWithLinq(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = values.OfType<int>().Sum();
        sw.Stop();
        Console.WriteLine("LINQ: {0} : {1}", sum, 
                          (long) sw.ElapsedMilliseconds);
    }
}

推荐答案

显然,JIT 编译器可以为第一种情况生成的机器代码效率更高.一个真正有用的规则是对象只能被拆箱为与装箱值具有相同类型的变量.这允许 JIT 编译器生成非常高效的代码,无需考虑值转换.

Clearly the machine code the JIT compiler can generate for the first case is much more efficient. One rule that really helps there is that an object can only be unboxed to a variable that has the same type as the boxed value. That allows the JIT compiler to generate very efficient code, no value conversions have to be considered.

is 运算符测试很简单,只需检查对象是否为空且是否为预期类型,只需一些机器代码指令即可.转换也很容易,JIT 编译器知道对象中值位的位置并直接使用它们.没有复制或转换发生,所有机器代码都是内联的,只需要大约十几个指令.在 .NET 1.0 中,当拳击很常见时,这需要非常有效.

The is operator test is easy, just check if the object isn't null and is of the expected type, takes but a few machine code instructions. The cast is also easy, the JIT compiler knows the location of the value bits in the object and uses them directly. No copying or conversion occurs, all machine code is inline and takes but about a dozen instructions. This needed to be really efficient back in .NET 1.0 when boxing was common.

强制转换为 int?需要做更多的工作.装箱整数的值表示与 Nullable 的内存布局不兼容.由于可能的盒装枚举类型,需要进行转换并且代码很棘手.JIT 编译器生成对名为 JIT_Unbox_Nullable 的 CLR 帮助程序函数的调用以完成工作.这是一个适用于任何值类型的通用函数,有很多代码来检查类型.并且值被复制.由于此代码被锁定在 mscorwks.dll 中,因此很难估计成本,但可能有数百条机器代码指令.

Casting to int? takes a lot more work. The value representation of the boxed integer is not compatible with the memory layout of Nullable<int>. A conversion is required and the code is tricky due to possible boxed enum types. The JIT compiler generates a call to a CLR helper function named JIT_Unbox_Nullable to get the job done. This is a general purpose function for any value type, lots of code there to check types. And the value is copied. Hard to estimate the cost since this code is locked up inside mscorwks.dll, but hundreds of machine code instructions is likely.

Linq OfType() 扩展方法也使用 is 运算符和强制转换.然而,这是对泛型类型的强制转换.JIT 编译器生成对辅助函数 JIT_Unbox() 的调用,该函数可以执行到任意值类型的强制转换.我没有很好的解释为什么它像转换为 Nullable 一样慢,因为应该需要更少的工作.我怀疑 ngen.exe 可能会在这里引起麻烦.

The Linq OfType() extension method also uses the is operator and the cast. This is however a cast to a generic type. The JIT compiler generates a call to a helper function, JIT_Unbox() that can perform a cast to an arbitrary value type. I don't have a great explanation why it is as slow as the cast to Nullable<int>, given that less work ought to be necessary. I suspect that ngen.exe might cause trouble here.

这篇关于带有“as"的性能惊喜和可空类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆