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

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

问题描述

我只是修改的C#第4章中深度可空类型的交易,而我加入了部分有关使用为操作符,它可以让你写的:

 对象o = ...;
诠释? X = O为int?;
如果(x.HasValue)
{
    ... //使用x.Value这里
}

我认为这是很整洁,而且它可以提高对C#1当量性能,用是,接着铸造 - 毕竟,这样我们只需要问的动态类型检查一次,然后一简单的值检查。

这似乎不是这样的情况,但是。我在下面列出了抽样检测的应用程序,这基本上概括对象数组内的所有整数 - 但阵列中含有大量的空引用和字符串引用以及盒装整数。基准测量code你必须以使用C#1,使用为操作符,只是踢一个LINQ的解决方案code。令我惊讶的是,C#$ C $ 1 c是在这种情况下,20倍的速度 - (由于涉及的迭代器,我不得不预期为慢,),甚至LINQ code击败为code。

是.NET执行 isinst 为空类型真的很慢?它是额外的 unbox.any 引起该问题?对此有另一种解释?目前,它感觉就像我将不得不把反对使用这种性能敏感的情况下警告...

结果:


  

演员:10000000:121

  为:10000000:2211

  LINQ:10000000:2143


code:

 使用系统;
使用System.Diagnostics程序;
使用System.Linq的;类测试
{
    const int的大小= 3000;    静态无效的主要()
    {
        [对象]值=新对象[尺寸]
        的for(int i = 0; I<尺寸 - 2; I + = 3)
        {
            值[I] =无效;
            值[I + 1] =;
            值第[i + 2] = 1;
        }        FindSumWithCast(值);
        FindSumWithAs(值);
        FindSumWithLinq(值);
    }    静态无效FindSumWithCast(对象[]值)
    {
        秒表SW = Stopwatch.StartNew();
        INT总和= 0;
        的foreach(在值对象o)
        {
            如果(邻为int)
            {
                INT X =(INT)O;
                总和+ = X;
            }
        }
        sw.Stop();
        Console.WriteLine(主演:{0}:{1},总之,
                          (长)sw.ElapsedMilliseconds);
    }    静态无效FindSumWithAs(对象[]值)
    {
        秒表SW = Stopwatch.StartNew();
        INT总和= 0;
        的foreach(在值对象o)
        {
            诠释? X = O为int?;
            如果(x.HasValue)
            {
                总和+ = x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine(为:{0}:{1},总之,
                          (长)sw.ElapsedMilliseconds);
    }    静态无效FindSumWithLinq(对象[]值)
    {
        秒表SW = Stopwatch.StartNew();
        INT总和= values​​.OfType&所述;诠释方式>()总和();
        sw.Stop();
        Console.WriteLine(LINQ:{0}:{1},总之,
                          (长)sw.ElapsedMilliseconds);
    }
}


解决方案

显然机器code中的JIT编译器可以产生第一种情况是更有效的。一个规则,真正帮助有一个对象只能是拆箱到具有相同类型的装箱值的变量。这使JIT编译器生成非常高效的code,没有价值的转换必须考虑。

的运营商的测试很容易,只要检查对象是不是null,而是预期的类型,但需要一些机器code指令。铸也很容易,JIT编译器知道该对象中的值的比特的位置,并直接使用它们。没有复制或转换时,所有机器code是inline和需要,但十几说明。这需要的是真正有效早在.NET 1.0当拳击是常见的。

铸造为int?需要大量的工作。盒装整数转口货值为presentation是不符合的内存布局可空下兼容&;&I​​NT GT; 。 A转换是必要的,code是棘手的,由于可能的盒装枚举类型。 JIT编译器生成一个名为JIT_Unbox_Nullable把工作做了CLR辅助函数的调用。这是任何值类型的通用功能,大量的code的那里检查类型。和值将被复制。很难估计,因为这code中的成本锁定了里面的Mscorwks.dll,但数以百计的机器code说明是可能的。

LINQ的OfType()扩展方法也使用的的运营商和演员。然而,这是一个强制转换为泛型类型。 JIT编译器生成一个辅助函数,JIT_Unbox(),可以执行强制转换为任意值类型的呼叫。我没有一个很好的解释为什么它是作为演员到慢可空< INT> ,由于较少的工作应该是必要的。我猜想,可能ngen.exe这里捣乱。

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
}

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.

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.

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

Results:

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

Code:

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

解决方案

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.

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.

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.

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.

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

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