8

考虑这段代码:

static void FillUsingAsNullable()
{
  int?[] arr = new int?[1 << 24];
  var sw = System.Diagnostics.Stopwatch.StartNew();
  for (int i = 0; i < arr.Length; ++i)
    arr[i] = GetObject() as int?;
  Console.WriteLine("{0:N0}", sw.ElapsedTicks);
}

static void FillUsingOwnCode()
{
  int?[] arr = new int?[1 << 24];
  var sw = System.Diagnostics.Stopwatch.StartNew();
  for (int i = 0; i < arr.Length; ++i)
  {
    object temporary = GetObject();
    arr[i] = temporary is int ? (int?)temporary : null;
  }
  Console.WriteLine("{0:N0}", sw.ElapsedTicks);
}

static object GetObject()
{
//Uncomment only one:
  //return new object();
  //return 42;
  //return null;
}

据我所知,方法FillUsingAsNullableFillUsingOwnCode应该是等价的。

但看起来“自己的代码”版本显然更快。

2编译“x86”或“x64”的2选项,编译“调试”或“发布(优化)”的3选项,以及返回GetObject方法的选项。据我所知,在所有这些2*2*3 == 12情况下,“自己的代码”版本明显快于“可空”版本。

问题:是不必要的asNullable<>,还是我在这里遗漏了什么(很可能)?

相关线程:“as”和可空类型的性能惊喜

4

3 回答 3

2

生成的 IL 是不同的,但不是根本性的。如果 JIT 是好的,它不是,这不是新闻,它可以编译成完全相同的 x86 代码。

我用 VS2010 Release AnyCPU 编译了这个。

as版本:

L_0015: call object ConsoleApplication3.Program::GetObject()
L_001a: stloc.3 
L_001b: ldloc.0 
L_001c: ldloc.2 
L_001d: ldelema [mscorlib]System.Nullable`1<int32>
L_0022: ldloc.3 
L_0023: isinst [mscorlib]System.Nullable`1<int32>
L_0028: unbox.any [mscorlib]System.Nullable`1<int32>
L_002d: stobj [mscorlib]System.Nullable`1<int32>

?:版本:

L_0015: call object ConsoleApplication3.Program::GetObject()
L_001a: stloc.3 
L_001b: ldloc.0 
L_001c: ldloc.2 
L_001d: ldelema [mscorlib]System.Nullable`1<int32>
L_0022: ldloc.3 
L_0023: isinst int32
L_0028: brtrue.s L_0036 //**branch here**
L_002a: ldloca.s nullable
L_002c: initobj [mscorlib]System.Nullable`1<int32>
L_0032: ldloc.s nullable
L_0034: br.s L_003c
L_0036: ldloc.3 
L_0037: unbox.any [mscorlib]System.Nullable`1<int32>
L_003c: stobj [mscorlib]System.Nullable`1<int32>

操作码的描述在 MSDN 上。理解这个 IL 并不难,任何人都可以做到。不过,对于没有经验的人来说,这有点费时。

主要区别在于源代码中有分支的版本在生成的IL中也有分支。它只是有点不那么优雅。如果 C# 编译器愿意,它可以对此进行优化,但团队的策略是让 JIT 担心优化。如果 JIT 获得了必要的投资,那就可以正常工作。

您可以通过查看 JIT 发出的 x86 来进一步分析这一点。你会发现一个明显的区别,但这将是一个不起眼的发现。我不会花时间去做那件事。


我修改了as版本以使用临时版本以进行公平比较:

            var temporary = GetObject();
            arr[i] = temporary as int?;
于 2014-01-27T20:31:50.887 回答
0

是的 as运算符是为了方便而不是性能,因此速度较慢。

有关更多信息,您可以参考此答案:

“as”关键字在内部是如何工作的?

于 2014-01-27T20:06:45.993 回答
0

我认为您的测试结果受到测量误差的支配。

这是我的程序:

    static void Main()
    {
        FillUsingAsNullable();
        FillUsingOwnCode();
        FillUsingAsNullable();
        FillUsingOwnCode();
        Console.ReadLine();
    }

这是我在调试器外部的 Release 中运行的内容return 42

2,540,125
1,975,131
2,407,204
2,246,339

请注意,运行之间存在相当大的差异。您可能需要连续运行多次才能获得良好的性能指标。

这是当我们更改GetObject()为 return时发生的情况(int?)42

7,829,214
7,941,861
8,001,102
7,124,096

同样,使用相同的配置:

8,243,258
7,114,879
7,932,285
7,268,167

如果你真的想收集有意义的数据,我建议以不同的顺序重复这两个测试,重复几次并查看结果均值和标准差。

我怀疑这些方法中最大的时间消耗是内存缓存失效,因此给定测试的结果可能取决于为每个测试分配的数组的确切位置以及在频繁的装箱分配期间何时发生 GC。

于 2014-01-27T21:01:51.210 回答