c# - 具有单个对象字段的结构如何比原始对象更快?

标签 c# .net performance

我有一个 struct,它包含一个 object 字段,以便更轻松地处理该对象。我想测试性能(我预计会有一些下降),但我得到了非常令人惊讶的结果。 带有 struct 的版本实际上更快:

Without box: 8.08 s

With box: 7.76 s

这怎么可能?

下面是重现结果的完整测试代码。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication68
{
    partial class Program
    {
        private const int Iterations = 100000000;

        static void Main(string[] args)
        {
            // Force JIT compilation.

            TimeWithoutBox(new MyObject());
            TimeWithoutBox(7);
            TimeBox(new MyObject());
            TimeBox(7);

            // The tests.

            var withoutBox = new TimeSpan();
            var box = new TimeSpan();

            for (int i = 0; i < 10; i++)
            {
                withoutBox += TimeWithoutBox(new MyObject());
                withoutBox += TimeWithoutBox(7);
                box += TimeBox(new MyObject());
                box += TimeBox(7);
            }

            Console.WriteLine("Without box: " + withoutBox);
            Console.WriteLine("With box: " + box);

            Console.ReadLine();
        }

        private static TimeSpan TimeBox(object value)
        {
            var box = new MyBox(value);

            var stopwatch = Stopwatch.StartNew();

            for (int i = 0; i < Iterations; i++)
            {
                TestBox(box);
            }

            return stopwatch.Elapsed;
        }

        private static TimeSpan TimeWithoutBox(object value)
        {
            var stopwatch = Stopwatch.StartNew();

            for (int i = 0; i < Iterations; i++)
            {
                TestWithoutBox(value);
            }

            return stopwatch.Elapsed;
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void TestBox(MyBox box)
        {
            if (box.IsDouble)
                TakeDouble((double)box.Value);
            else if (box.IsObject)
                TakeObject((MyObject)box.Value);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void TestWithoutBox(object box)
        {
            if (box.GetType() == typeof(double))
                TakeDouble((double)box);
            else if (box.GetType() == typeof(MyObject))
                TakeObject((MyObject)box);
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void TakeDouble(double value)
        {
            // Empty method to force consuming the cast.
        }

        [MethodImpl(MethodImplOptions.NoInlining)]
        private static void TakeObject(MyObject value)
        {
            // Empty method to force consuming the cast.
        }
    }

    struct MyBox
    {
        private readonly object _value;

        public object Value
        {
            get { return _value; }
        }

        public MyBox(object value)
        {
            _value = value;
        }

        public bool IsDouble
        {
            get { return _value.GetType() == typeof(double); }
        }

        public bool IsObject
        {
            get { return _value.GetType() == typeof(MyObject); }
        }
    }

    class MyObject
    {
    }
}

编辑:

我已将 IsDoubleIsObject 测试更改为与其他测试具有相同的语句。我重新执行了该应用程序,结果时间完全相同。

编辑 2:

此代码是使用 Release 版本测试的,在 32 位编译,没有附加调试器; .NET 4.5 和 Visual Studio 2012。针对 64 位编译它会得到截然不同的结果;在我的机器上:

Without box: 8.23 s

With box: 16.99 s

最佳答案

我复制了准确​​的代码,在没有调试器的情况下运行了 Release(这两个都很重要!)并且在 x64 上运行。结果:

Without box: 00:00:07.9650541
With box: 00:00:16.0958162

将测试更改为:

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static void TestBox(MyBox box)
    {
        if (box.Value.GetType() == typeof(double))
            TakeDouble((double)box.Value);
        else if (box.Value.GetType() == typeof(MyObject))
            TakeObject((MyObject)box.Value);
    }

使运行时间几乎相等:

Without box: 00:00:07.9488281
With box: 00:00:08.6084029

为什么?因为 JIT 决定不内联 IsDouble 并且手动内联有帮助。这很奇怪,因为它是一个很小的函数。第 13 行的 call 就是这个调用。

enter image description here

现在为什么还有一些性能差异? .NET JIT 并不是目前最好的编译器……可能有些指令略有不同。对比一下两个版本的反汇编就可以知道了。我没有时间这样做,因为我希望这种差异不会很有趣。

我希望 C 编译器能够做到这一点。该结构应该表现得像它包含的单个 object 成员。应该内联小方法。使用当今的编译器技术,这绝对是可行的。让我们希望下一代 JIT 和 NGEN 能够做到这一点。目前正在开发新的 JIT (RyuJIT),他们正在将优化从 VC 后端转移到 NGEN(最近宣布)。

关于c# - 具有单个对象字段的结构如何比原始对象更快?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20348371/

相关文章:

linux - 仅显示带有 Perf 注释的源代码

c# - 如何使用返回的 linq 变量?

c# - 使用 HtmlAgilityPack 在 C# 中截屏

c# - 处理 asp.net mvc 3 Controller 中的重复代码

c# - 我可以像在 VB 中那样在 C# 中为控件提供索引属性吗?

c# - TakeUntil 没有按记录工作?

C# 套接字 : How do you handle a socket in C#. NET 在 VB6 或 Delphi(事件驱动)中处理它们的方式?

c# - 客户端如何知道 wcf 服务正在运行?

MYSQL avg time in where 子句

sql - 表锁会加速 Oracle 10g 企业版中的更新语句吗?