“ as”和可为空的类型带来的性能惊喜


330

我只是在修改C#中有关可空类型的第4章,并添加了有关使用“ as”运算符的部分,该部分允许您编写:

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

我认为这确实很整洁,并且可以使用“ is”后跟强制类型转换来提高C#1等效项的性能-毕竟,这种方式我们只需要进行一次动态类型检查,然后进行简单的值检查。

但是,情况似乎并非如此。我在下面提供了一个示例测试应用程序,该应用程序基本上将对象数组中的所有整数相加-但该数组包含许多空引用和字符串引用以及装箱的整数。该基准测试可测量您在C#1中必须使用的代码,这些代码使用“ as”运算符,并且仅用于启动LINQ解决方案。令我惊讶的是,在这种情况下,C#1代码的速度提高了20倍-甚至LINQ代码(考虑到涉及的迭代器,我希望它也会更慢)击败了“ as”代码。

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

结果:

演员:10000000:121
身份: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);
    }
}

8
为什么不看一下固定代码?甚至VS调试器也可以显示它。
2009年

2
我很好奇,您是否也使用CLR 4.0进行了测试?
Dirk Vollmar,09年

1
@安东:好点。会在某个时候做(尽管目前尚不支持VS :) @divo:是的,而且整个情况都更糟。但这是beta版本,因此那里可能有很多调试代码。
乔恩·斯基特

1
今天,我了解到您可以as在可为空的类型上使用。有趣,因为它不能在其他值类型上使用。实际上,更令人惊讶。
嬉皮

3
@Lepp对于不使用值类型非常有意义。考虑一下,as尝试转换为类型,如果失败,则返回null。您不能将值类型设置为null
Earlz 2010年

Answers:


209

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

运营商的测试很容易,只要检查对象是不是null,而是预期的类型的,但需要一些机器代码指令。转换也很容易,JIT编译器知道值位在对象中的位置并直接使用它们。没有复制或转换发生,所有机器代码都是内联的,只需要十几条指令。当装箱很普遍时,这在.NET 1.0中必须非常有效。

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

Linq OfType()扩展方法还使用is运算符和强制类型转换。但是,这是强制转换为通用类型。JIT编译器会生成对辅助函数JIT_Unbox()的调用,该函数可以将类型转换为任意值类型。Nullable<int>考虑到需要的工作量较少,我对此没有很好的解释。我怀疑ngen.exe可能会在这里引起麻烦。


16
好吧,我坚信。我想我曾经认为“ is”可能是昂贵的,因为有可能沿继承层次结构前进-但是在值类型的情况下,没有层次结构的可能性,因此可以进行简单的按位比较。我仍然认为,对于可为空的情况,JIT代码可以比现在进行更多优化。
乔恩·斯基特

26

在我看来,isinst对于可空类型而言,这真的很慢。在方法上FindSumWithCast我改变了

if (o is int)

if (o is int?)

这也大大降低了执行速度。我能看到的IL的唯一区别是

isinst     [mscorlib]System.Int32

被更改为

isinst     valuetype [mscorlib]System.Nullable`1<int32>

1
不仅如此;在“铸态”情况下,isinst之后是用于无效的测试,然后有条件unbox.any。在可为null的情况下,存在无条件 unbox.any
乔恩·斯基特

是的,事实证明这两者 都是可空类型isinstunbox.any但速度较慢。
Dirk Vollmar,09年

@Jon:您可以查看我的答案,以了解为什么需要演员表。(我知道这很旧,但是我刚刚发现了这个q,以为我应该提供有关CLR的2c信息)。
约翰内斯·鲁道夫

22

最初,这是对Hans Passant出色答案的评论,但由于时间太长,因此我想在此处添加一些内容:

首先,C#as运算符将发出一条isinstIL指令(运算符也将发出is)。(另一个有趣的指令是castclass,当您执行直接强制转换并且编译器知道不能忽略运行时检查时发出。)

这是做什么的isinstECMA 335 Partition III,4.6):

格式:isinst 类型

typeTok是元数据令牌(一个typereftypedeftypespec),这表明所期望的类。

如果typeTok是不可为空的值类型或通用参数类型,则将其解释为“盒装” typeTok

如果typeTok是可为空的类型,Nullable<T>则将其解释为“装箱”T

最重要的是:

如果obj的实际类型(不是验证者跟踪的类型)是可验证者可分配给 typeTok的类型,则isinst成功,并且obj(作为结果)不变地返回,而验证将其跟踪为typeTok与强制(§1.6)和转换(§3.27)不同,isinst永远不要更改对象的实际类型并保留对象标识(请参阅分区I)。

因此,性能杀手不是isinst这种情况,而是其他杀手unbox.any。从Hans的回答中并不清楚,因为他只看了JITed代码。通常,C#编译器会在unbox.any之后发出一个after isinst T?(但isinst T如果T是引用类型,则将其省略)。

为什么这样做呢?isinst T?永远不会有明显的效果,即返回a T?。相反,所有这些说明都确保您"boxed T"可以将拆箱T?。为了获得一个实际的T?,我们仍然需要拆箱我们"boxed T"T?,这就是为什么编译器发出unbox.anyisinst。如果您考虑一下,这是有道理的,因为for的“框格式” T?仅仅是a,"boxed T"并且制作castclassisinst执行unbox会不一致。

使用标准中的一些信息来备份Hans的发现,结果如下:

(ECMA 335 Partition III,4.33): unbox.any

当应用于值类型的框式形式时,unbox.any指令将提取obj(类型为O)中包含的值。(等效于unbox后跟ldobj。)当应用于引用类型时,该unbox.any指令与castclasstypeTok 具有相同的效果。

(ECMA 335 Partition III,4.32): unbox

通常,unbox仅计算盒装对象内部已经存在的值类型的地址。将可空值类型装箱时,这种方法是不可能的。因为在box操作期间将Nullable<T>值转换为boxed Ts,所以实现通常必须Nullable<T>在堆上制造新的对象并计算到新分配的对象的地址。


我认为最后引用的句子可能有错字。不应“……在堆上 ……”是“在执行堆栈上?” 好像取消装箱回到一些新的GC堆实例中,原来的问题换成了几乎相同的新问题。
Glenn Slayden

19

有趣的是,我传递了关于操作员支持的反馈dynamic,其速度降低了一个数量级Nullable<T>(类似于此早期测试)-我怀疑原因非常相似。

要爱Nullable<T>。另一个有趣的是,即使JIT可以发现(并删除)null非可空结构,也可以将其用于Nullable<T>

using System;
using System.Diagnostics;
static class Program {
    static void Main() { 
        // JIT
        TestUnrestricted<int>(1,5);
        TestUnrestricted<string>("abc",5);
        TestUnrestricted<int?>(1,5);
        TestNullable<int>(1, 5);

        const int LOOP = 100000000;
        Console.WriteLine(TestUnrestricted<int>(1, LOOP));
        Console.WriteLine(TestUnrestricted<string>("abc", LOOP));
        Console.WriteLine(TestUnrestricted<int?>(1, LOOP));
        Console.WriteLine(TestNullable<int>(1, LOOP));

    }
    static long TestUnrestricted<T>(T x, int loop) {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
    static long TestNullable<T>(T? x, int loop) where T : struct {
        Stopwatch watch = Stopwatch.StartNew();
        int count = 0;
        for (int i = 0; i < loop; i++) {
            if (x != null) count++;
        }
        watch.Stop();
        return watch.ElapsedMilliseconds;
    }
}

尤瑟 那真是一个痛苦的区别。真是的
乔恩·斯基特

如果所有这些都没有其他好处,那就让我在原始代码代码中都加上警告:)
Jon Skeet

我知道这是一个古老的问题,但是您能否解释一下“ null非空结构的JIT定位(和删除)”是什么意思?您是说它null在运行时替换为默认值或其他值吗?
贾斯汀·摩根

2
@Justin-泛型方法可以在运行时使用任何数量的泛型参数(T等)排列。堆栈等要求取决于args(局部堆栈的堆栈空间等),因此对于涉及值类型的任何唯一置换,您都会获得一个JIT。但是,引用的大小都相同,因此共享一个JIT。在执行按值类型的JIT时,它可以检查一些明显的情况,并尝试消除由于无法实现空值之类的不可达代码。请注意,这并不完美。另外,我也忽略了上述的AOT。
Marc Gravell

无限制的可空测试仍然要慢2.5个数量级,但是当您不使用count变量时,会进行一些优化。在这两种情况下添加Console.Write(count.ToString()+" ");后,watch.Stop();会使其他测试的速度降低一个数量级,但是不受限制的可空测试不会更改。请注意,在测试通过的情况时null,还会发生变化,以确认原始代码并未真正对其他测试进行null检查和递增。Linqpad
马克·赫德

12

这是上面的FindSumWithAsAndHas的结果: 替代文字

这是FindSumWithCast的结果: 替代文字

发现:

  • 使用as,它首先测试一个对象是否是Int32的实例;它在后台使用isinst Int32(类似于手写代码:if(o is int))。并使用as,也可以无条件地将对象拆箱。调用属性(它仍然是函数)是一个真正的性能杀手,IL_0027

  • 使用强制转换,首先测试object是否为int if (o is int); 在引擎盖下使用isinst Int32。如果它是int的实例,则可以安全地将值IL_002D拆箱。

简而言之,这是using as方法的伪代码:

int? x;

(x.HasValue, x.Value) = (o isinst Int32, o unbox Int32)

if (x.HasValue)
    sum += x.Value;    

这是使用强制转换方法的伪代码:

if (o isinst Int32)
    sum += (o unbox Int32)

因此,强制转换((int)a[i],语法看起来很像强制转换,但实际上是取消装箱,强制转换和取消装箱使用相同的语法,下一次我将使用正确的术语进行学问)方法确实更快,您只需要取消对值的装箱当一个对象绝对是一个对象时int。使用as方法不能说同一件事。


11

为了使此答案保持最新,值得一提的是,此页面上的大部分讨论现在都与C#7.1.NET 4.7一起进行,而后者支持一种苗条的语法,该语法还可以生成最佳的IL代码。

OP的原始示例...

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

变得简单...

if (o is int x)
{
    // ...use x in here
}

我发现新语法的一种常见用法是在编写实现(大多数情况下)的.NET 值类型时(即structC#中IEquatable<MyStruct>)。在实现强类型Equals(MyStruct other)方法之后,您现在可以按如下所示正常地将未类型的Equals(Object obj)替代(从继承Object)重定向到它:

public override bool Equals(Object obj) => obj is MyStruct o && Equals(o);

 


附录:此处分别给出了此答案中上面显示的前两个示例函数的Release构建IL代码。尽管新语法的IL代码确实小了1个字节,但通过进行零次调用(相对于两次调用)并unbox在可能的情况下完全避免该操作,它通常会大获成功。

// static void test1(Object o, ref int y)
// {
//     int? x = o as int?;
//     if (x.HasValue)
//         y = x.Value;
// }

[0] valuetype [mscorlib]Nullable`1<int32> x
        ldarg.0
        isinst [mscorlib]Nullable`1<int32>
        unbox.any [mscorlib]Nullable`1<int32>
        stloc.0
        ldloca.s x
        call instance bool [mscorlib]Nullable`1<int32>::get_HasValue()
        brfalse.s L_001e
        ldarg.1
        ldloca.s x
        call instance !0 [mscorlib]Nullable`1<int32>::get_Value()
        stind.i4
L_001e: ret

// static void test2(Object o, ref int y)
// {
//     if (o is int x)
//         y = x;
// }

[0] int32 x,
[1] object obj2
        ldarg.0
        stloc.1
        ldloc.1
        isinst int32
        ldnull
        cgt.un
        dup
        brtrue.s L_0011
        ldc.i4.0
        br.s L_0017
L_0011: ldloc.1
        unbox.any int32
L_0017: stloc.0
        brfalse.s L_001d
        ldarg.1
        ldloc.0
        stind.i4
L_001d: ret

有关进一步证明我对新C#7语法性能超过先前可用选项的评论的进一步测试,请参见此处(特别是示例“ D”)。


9

进一步剖析:

using System;
using System.Diagnostics;

class Program
{
    const int Size = 30000000;

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

        FindSumWithIsThenCast(values);

        FindSumWithAsThenHasThenValue(values);
        FindSumWithAsThenHasThenCast(values);

        FindSumWithManualAs(values);
        FindSumWithAsThenManualHasThenValue(values);



        Console.ReadLine();
    }

    static void FindSumWithIsThenCast(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("Is then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenValue(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 then Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenHasThenCast(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (x.HasValue)
            {
                sum += (int)o;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Has then Cast: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithManualAs(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            bool hasValue = o is int;
            int x = hasValue ? (int)o : 0;

            if (hasValue)
            {
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Manual As: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithAsThenManualHasThenValue(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {
            int? x = o as int?;

            if (o is int)
            {
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("As then Manual Has then Value: {0} : {1}", sum,
                            (long)sw.ElapsedMilliseconds);
    }

}

输出:

Is then Cast: 10000000 : 303
As then Has then Value: 10000000 : 3524
As then Has then Cast: 10000000 : 3272
Manual As: 10000000 : 395
As then Manual Has then Value: 10000000 : 3282

从这些数字中我们可以得出什么?

  • 首先,是-然后铸造方法是显著的速度比的做法。303和3524
  • 其次,.value比强制转换慢一点。3524和3272
  • 第三,.HasValue比使用手动has稍慢(即使用is)。3524和3282
  • 第四,在模拟方式真实方式之间进行苹果对苹果的比较(即同时分配模拟的HasValue和转换模拟值都发生),我们可以看到模拟方式仍然比真实方式快得多。395和3524
  • 最后,基于第一和第四个结论,有一些错误 实现^ _ ^

8

我没有时间尝试,但是您可能想要:

foreach (object o in values)
        {
            int? x = o as int?;

int? x;
foreach (object o in values)
        {
            x = o as int?;

您每次都在创建一个新对象,虽然不能完全解释问题,但可能会有所帮助。


1
不,我跑过了,速度慢了一点。
Henk Holterman

2
在我的经验中,在另一个位置声明变量只会在捕获变量时才显着影响生成的代码(此时会影响实际的语义)。请注意,尽管它肯定是int?使用来在堆栈上创建新的实例,但它并没有在堆上创建新对象unbox.any。我怀疑这就是问题所在-我的猜测是,手工制作的IL可能在这里击败了这两种选择...虽然JIT也有可能经过优化以识别is / cast情况,并且只检查一次。
乔恩·斯基特

我一直在考虑演员表的优化,因为它已经存在了很长时间。
詹姆斯·布莱克

1
is / cast是优化的一个简单目标,这是一个令人讨厌的惯用语。
安东·泰克(09年

4
创建方法的堆栈框架时,将在堆栈上分配局部变量,因此在方法中声明变量的位置完全没有区别。(除非它当然处于关闭状态,但事实并非如此。)
Guffa

8

我尝试了确切的类型检查构造

typeof(int) == item.GetType(),其执行速度与item is int版本一样快,并且始终返回数字(强调:即使您Nullable<int>向数组写入了a ,也需要使用typeof(int))。您还需要null != item在此处进行其他检查。

然而

typeof(int?) == item.GetType()保持快速状态(与相对item is int?),但始终返回false。

在我看来,typeof-construct是使用RuntimeTypeHandle 进行准确类型检查的最快方法。由于这种情况下的确切类型与nullable不匹配,因此我想is/as必须在此进行额外的繁琐工作,以确保它实际上是Nullable类型的实例。

老实说:您is Nullable<xxx> plus HasValue买了什么?没有。您总是可以直接转到基础(值)类型(在这种情况下)。您要么得到值,要么是“否,不是您要查询的类型的实例”。即使您写入(int?)null数组,类型检查也将返回false。


有意思... ...使用的想法“为” +的HasValue(不加的HasValue,注)是它的唯一执行类型检查一次,而不是两次。它一步完成“检查并取消装箱”。感觉应该更快一些……但事实并非如此。我不确定最后一句话是什么意思,但是没有装箱的东西int?-如果装箱一个int?值,它最终将作为装箱的int或null引用。
乔恩·斯基特

7
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);
        FindSumWithAsAndHas(values);
        FindSumWithAsAndIs(values);


        FindSumWithIsThenAs(values);
        FindSumWithIsThenConvert(values);

        FindSumWithLinq(values);



        Console.ReadLine();
    }

    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 FindSumWithAsAndHas(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 and Has: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }


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







    static void FindSumWithIsThenAs(object[] values)
    {
        // Apple-to-apple comparison with Cast routine above.
        // Using the similar steps in Cast routine above,
        // the AS here cannot be slower than Linq.



        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {

            if (o is int)
            {
                int? x = o as int?;
                sum += x.Value;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then As: {0} : {1}", sum,
                          (long)sw.ElapsedMilliseconds);
    }

    static void FindSumWithIsThenConvert(object[] values)
    {
        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        foreach (object o in values)
        {            
            if (o is int)
            {
                int x = Convert.ToInt32(o);
                sum += x;
            }
        }
        sw.Stop();
        Console.WriteLine("Is then Convert: {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);
    }
}

输出:

Cast: 10000000 : 456
As and Has: 10000000 : 2103
As and Is: 10000000 : 2029
Is then As: 10000000 : 1376
Is then Convert: 10000000 : 566
LINQ: 10000000 : 1811

[编辑:2010-06-19]

注意:先前的测试是在VS内部进行的,使用VS2009,Core i7(公司开发机器)进行配置调试。

以下操作是在我的计算机上使用VS2010在Core 2 Duo上完成的

Inside VS, Configuration: Debug

Cast: 10000000 : 309
As and Has: 10000000 : 3322
As and Is: 10000000 : 3249
Is then As: 10000000 : 1926
Is then Convert: 10000000 : 410
LINQ: 10000000 : 2018




Outside VS, Configuration: Debug

Cast: 10000000 : 303
As and Has: 10000000 : 3314
As and Is: 10000000 : 3230
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 418
LINQ: 10000000 : 1944




Inside VS, Configuration: Release

Cast: 10000000 : 305
As and Has: 10000000 : 3327
As and Is: 10000000 : 3265
Is then As: 10000000 : 1942
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1932




Outside VS, Configuration: Release

Cast: 10000000 : 301
As and Has: 10000000 : 3274
As and Is: 10000000 : 3240
Is then As: 10000000 : 1904
Is then Convert: 10000000 : 414
LINQ: 10000000 : 1936

您不感兴趣使用的是哪个框架版本?(使用.NET 4RC)在我的上网本的结果更为显着-版本使用As是很多比你更差的结果。也许他们已经对.NET 4 RTM进行了改进?我仍然认为它可能会更快...
Jon Skeet 2010年

@Michael:您是在运行未优化的版本还是在调试器中运行?
乔恩·斯基特

@Jon:未优化的构建,由调试器执行
Michael Buen 2010年

1
@Michael:对-我倾向于将调试器下的性能结果视为基本无关:)
Jon Skeet 2010年

@Jon:如果是在调试器下进行,则表示在VS内部;是的,以前的基准测试是在调试器下完成的。我再次在VS内部和外部进行基准测试,并分别编译为调试和发布。检查编辑
Michael Buen 2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.