使用 "as" 和可空类型的性能惊喜

345

我正在修改《深入理解C#》第四章关于可空类型的部分,并添加了一个使用 "as" 运算符的小节,该运算符允许您编写以下代码:

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

我觉得这个方法很棒,可以比C# 1中使用“is”后跟强制转换的方式提高性能-毕竟,这样我们只需要请求一次动态类型检查,然后进行简单的值检查。

不过事实并非如此。下面的示例测试应用程序包含了一个对象数组,其中包含许多空引用、字符串引用和装箱整数,它们的求和功能可以作为基准测试。该基准测试分别测量了在C# 1中使用的代码、使用“as”运算符的代码以及仅供娱乐的LINQ解决方案。令我惊讶的是,在这种情况下,C# 1的代码要快20倍——甚至LINQ代码(考虑到涉及迭代器)也要击败“as”代码。

是.NET对可空类型的isinst实现太慢了吗?是额外的unbox.any导致问题吗?还有其他解释吗?目前感觉我必须警告在性能敏感的情况下不要使用这个方法……

结果:

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

8
为什么不看一下即时编译的代码呢?甚至VS调试器也可以展示它。 - Anton Tykhyy
2
我只是好奇,你也测试过CLR 4.0吗? - Dirk Vollmar
1
今天我学到了在可空类型上可以使用 as。有趣的是,它不能用于其他值类型。实际上,更令人惊讶。 - leppie
3
@Lepp 这样不支持值类型完全合乎逻辑。想一想,as 操作符试图将一个对象强制转换为某个类型,如果失败就返回 null。而值类型无法被设置为 null。 - Earlz
1
仅为完整起见:从其中一个答案中提出的想法,使用 if (o != null && o.GetType() == typeof(int)) { int x = (int)o; ... 几乎与使用 is 一样快,而且比“as 可空”或 LINQ 的方式要快得多。我猜这样我们会两次取消装箱 int。(当类型被密封时,例如结构体,is 检查和 GetType() == something 检查之间没有太大区别。) - Jeppe Stig Nielsen
显示剩余18条评论
10个回答

217

显然,JIT编译器为第一种情况生成的机器代码更加高效。有一个规则可以真正帮助这里,即一个对象只能解封箱为与装箱值相同类型的变量。这使得JIT编译器能够生成非常高效的代码,无需考虑任何值转换。

is运算符测试很容易,只需检查对象是否不为空且为预期类型,只需几个机器码指令。强制转换也很容易,JIT编译器知道对象中值位的位置并直接使用它们。没有复制或转换发生,所有机器码都是内联的,只需大约十几条指令。在.NET 1.0中,这必须非常高效,因为装箱很常见。

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

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


18
好的,我被说服了。我想我习惯于将“is”视为潜在昂贵的,因为可能会沿着继承层次结构向上查找 - 但对于值类型,不存在层次结构的可能性,因此可以进行简单的位比较。尽管如此,我仍然认为 JIT 代码对可空情况的优化可以比目前更加重视。 - Jon Skeet

27

在可空类型上,isinst 似乎非常缓慢。在 FindSumWithCast 方法中,我进行了修改:

if (o is int)
to
if (o is int?)

这也显著地减慢了执行速度。我能看到的IL唯一的区别是

isinst     [mscorlib]System.Int32

变成

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

2
不仅如此,在“转换”情况下,isinst 后面跟着一个空值测试,然后 有条件地 进行 unbox.any。在可空情况下,是 无条件的 unbox.any - Jon Skeet
是的,结果证明isinstunbox.any在可空类型上都比较慢。 - Dirk Vollmar
@Jon:你可以查看我的答案,了解为什么需要转换类型。(我知道这很老,但我刚刚发现这个问题,并想提供我对CLR所知道的2c)。 - Johannes Rudolph

22

这最初是作为对Hans Passant的优秀答案的评论开始的,但我认为它太长了,所以我想在这里添加一些内容:

首先,C#的as运算符将发出一个isinst IL指令(is运算符也是如此)。 (另一个有趣的指令是castclass,当您进行直接转换并且编译器知道可以省略运行时检查时,会发出该指令。)

这就是isinst所做的事情(ECMA 335 Partition III, 4.6):

格式:isinst typeTok

typeTok是元数据标记(typereftypedeftypespec),表示所需的类。

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

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

最重要的是:

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

因此,在这种情况下,性能杀手不是isinst,而是额外的unbox.any。从Hans的答案中并不清楚这一点,因为他只看了JIT的代码。通常,C#编译器将在isinst T?之后发出unbox.any(但在T是引用类型时将省略它)。

为什么会这样呢?isinst T?没有显而易见的效果,也就是说您不会得到一个T?。相反,所有这些指令只是确保您拥有一个"boxed T",可以将其解包成T?。要获取实际的T?,我们仍然需要将我们的"boxed T"解包为T?,这就是为什么编译器在isinst之后发出unbox.any的原因。如果您考虑一下,这很有意义,因为T?的"盒装格式"只是一个"boxed T",使castclassisinst执行解包操作将是不一致的。
以下是关于Hans所发现的一些信息:标准(ECMA 335第三部分,4.33):unbox.any

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

(ECMA 335第三部分,4.32):unbox

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


我认为最后引用的那句话可能有一个打字错误;“...on the heap...”不应该是“在执行栈上吗?”似乎将拆箱回到一些新的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
如果没有其他好处,这让我在我的原始代码和这个代码中都加入了警告。 - Jon Skeet
我知道这是一个老问题,但你能解释一下你所说的“JIT在非可空结构体中检测到(并删除)null”的意思吗?你是指它在运行时用默认值或其他东西替换了null吗? - Justin Morgan
2
@Justin - 一个通用方法可以在运行时使用任意数量的泛型参数(T等)。堆栈等需求取决于参数(本地堆栈空间的数量等),因此您可以为涉及值类型的任何唯一排列获取一个JIT。然而,引用都是相同大小的,因此共享一个JIT。在执行每个值类型的JIT时,它可以检查一些明显的情况,并尝试删除由于诸如不可能的空值之类的事物而导致无法访问的代码。请注意,它并不完美。此外,我忽略了上述内容的AOT。 - Marc Gravell
无限制的可空测试仍然比其他测试慢2.5个数量级,但是当您不使用“count”变量时,会进行一些优化。在两种情况下,在watch.Stop();之后添加Console.Write(count.ToString()+" ");会使其他测试的速度减少不到一个数量级,但是无限制的可空测试没有改变。请注意,在测试传递“null”时也会发生更改,确认原始代码实际上并没有对其他测试进行空检查和增量操作。Linqpad - Mark Hurd

15
为了保持这个答案的最新性,值得一提的是,现在大部分讨论已经过时了,因为C# 7.1.NET 4.7支持一种简洁的语法,同时也产生了最好的IL代码。
原始问题的示例...
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值类型(即C#中的struct)时实现IEquatable<MyStruct>(大多数情况下应该这样做)。在实现了强类型的Equals(MyStruct other)方法之后,您现在可以如下优雅地重定向未经类型化的Equals(Object obj)覆盖(从Object继承):

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

附录:本答案中展示的前两个示例函数的“发布”版本 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”)。


12
这是从FindSumWithAsAndHas方法得出的结果:alt text 这是从FindSumWithCast方法得出的结果:alt text 结论如下:
- 使用 as,首先测试对象是否为 Int32 类型的实例;在内部,它使用isinst Int32进行判断(类似于手写代码: if (o is int))。然后,使用 as 无条件地对对象进行拆箱操作。调用属性(在底层仍然是一个函数)会极大地降低性能,IL_0027。 - 使用 cast,首先测试对象是否为 int 类型的实例 if (o is int);在内部,它使用isinst Int32进行判断。如果是 int 的实例,则可以安全地对其进行拆箱,IL_002D。
简单来说,以下是使用 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])这个语法看起来像是一个类型转换,但实际上是拆箱。虽然类型转换和拆箱在语法上很相似,但下次我会严谨地用正确的术语。使用强制类型转换(cast)方法可以更快速,只有在对象确切为时才需要拆箱(unboxing)。而使用as方法则无法做到这一点。


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

这些数字告诉我们什么?
1.首先,is-then-cast方法比as方法快得多。303对3524。
2.其次,.Value稍微比强制转换慢一点。3524对3272。
3.第三,.HasValue比手动has (即使用is)略慢。3524对3282。
4.在模拟HasValue和转换Value的同时进行逐一比较(即苹果对苹果的比较), 我们可以看到模拟的as仍然比真正的as快很多。395对3524。
5.最后,根据第一和第四个结论,as实现有问题。^_^

8

我没有时间去尝试,但你可能想要:

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

as

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

您每次都在创建一个新对象,这并不能完全解释问题,但可能会对问题有所贡献。

1
不,我运行了那个程序,速度稍微慢一些。 - H H
2
在我的经验中,只有在捕获变量时(此时它会影响实际语义),在不同位置声明变量才会显着影响生成的代码。请注意,这并不是在堆上创建新对象,尽管它肯定使用unbox.any在堆栈上创建了一个新的int?实例。我怀疑这就是问题所在——我猜手工编写的IL代码可以击败这两个选项……虽然JIT也可能被优化为识别is/cast情况并仅检查一次。 - Jon Skeet
1
is/cast是优化的易攻击目标,它是一个非常恼人的常见习语。 - Anton Tykhyy
4
当方法的堆栈帧被创建时,局部变量会分配在堆栈上,因此在方法中声明变量的位置并不重要(除非它是在闭包中,但这不是本例的情况)。 - Guffa
抖动在这些情况下自动使用相同的变量(当它没有被 lambda 或其他任何东西捕获时)。这已经得到了 Eric Lippert 的确认。 - configurator
显示剩余2条评论

8

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

typeof(int) == item.GetType(),它的执行速度与item is int版本相同,并且始终返回数字(强调:即使您将Nullable<int>写入数组中,您仍需要使用typeof(int))。在此处还需要进行额外的null!= item检查。

然而

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

在我的眼中,typeof构造是最快的确切类型检查方式,因为它使用RuntimeTypeHandle。由于这种情况下的确切类型与可空类型不匹配,我猜想,is/as必须在确保它实际上是Nullable类型的实例时进行额外的重载。

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


有趣...使用“as”+HasValue(注意不是is加上HasValue)的想法是它只执行一次类型检查而不是两次。它在单个步骤中执行“检查和取消装箱”。这感觉应该更快...但显然不是。我不确定您最后一句话的意思,但没有所谓的boxed int? - 如果将int?值装箱,则其最终成为装箱的int或null引用。 - Jon Skeet

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内部执行的,配置为debug,使用了VS2009,在公司的i7电脑上进行。

以下测试是在我的机器上进行的,使用Core 2 Duo处理器,使用了VS2010。

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),结果更加惊人 - 使用A的版本比你的结果要差得多。也许他们已经在.NET 4 RTM中改进了它?我仍然认为它可以更快... - Jon Skeet
@Michael:你是在运行未优化的构建版本,还是在调试器中运行? - Jon Skeet
@Jon:未经优化的构建,在调试器下。 - Michael Buen
1
@Michael:没错 - 我倾向于认为在调试器下查看性能结果基本上是无关紧要的 :) - Jon Skeet
@Jon:如果“在调试器下”指的是在VS内部,那么之前的基准测试确实是在调试器下完成的。我会再次进行基准测试,包括在VS内部和外部进行编译,以及编译为调试版本和发布版本。请查看编辑内容。 - Michael Buen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接