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

C# 具有单个对象字段的结构如何比原始对象更快?,c#,.net,performance,C#,.net,Performance,我有一个struct,它保存一个对象字段,以便更轻松地使用该对象。我想测试性能(我预计性能会下降),但我得到了非常令人惊讶的结果带有结构的版本实际上更快: 不带盒子:8.08秒 带方框:7.76秒 这怎么可能? 下面是复制结果的完整测试代码 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerService

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

不带盒子:8.08秒

带方框:7.76秒

这怎么可能?

下面是复制结果的完整测试代码

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
    {
    }
}
使用系统;
使用System.Collections.Generic;
使用系统诊断;
使用System.Linq;
使用System.Runtime.CompilerServices;
使用系统文本;
使用System.Threading.Tasks;
命名空间控制台应用程序68
{
部分类程序
{
私有常量int迭代次数=100000000;
静态void Main(字符串[]参数)
{
//强制JIT编译。
TimeWithoutBox(新的MyObject());
不带盒子的时间(7);
TimeBox(新的MyObject());
计时器(7);
//测试。
var withoutBox=new TimeSpan();
变量框=新的时间跨度();
对于(int i=0;i<10;i++)
{
withoutBox+=时间withoutBox(新的MyObject());
不带盒子+=不带盒子的时间(7);
box+=时间框(新的MyObject());
盒子+=时间盒(7);
}
控制台写入线(“无框:+无框”);
Console.WriteLine(“带框:+box”);
Console.ReadLine();
}
专用静态时间跨度时间框(对象值)
{
变量框=新的MyBox(值);
var stopwatch=stopwatch.StartNew();
对于(int i=0;i
编辑:

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

EDIT2:

这段代码是使用版本进行测试的,编译为32位,没有附加调试程序。NET 4.5和Visual Studio 2012。根据64位编译它会得到截然不同的结果;在我的机器上:

不带方框:8.23秒

带方框:16.99秒


我复制了准确的代码,在没有调试器的情况下运行了它的发行版(这两个都很重要!),并在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行的
调用
就是这个调用

现在为什么还有一些性能差异?NET JIT不是最好的编译器。。。可能有些说明有点不同。您可以通过比较两个版本的反汇编来找到答案。我没有时间,因为我预计两者之间的差别会很小


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

1亿次迭代中的32ms差异?谁在乎呢。你怎么知道你不是在间接测试
IsDouble
IsObject
GetType()
typeof()
的效率?你使用过哪个visual studio版本?你重复了测试吗?您是否尝试过不同的构建——发布/调试。。。。还有一点好奇。。。。。你还试过单声道吗?@Dan-o:你说得很对,但我觉得这很奇怪,我想知道发生了什么。@Dan-o:顺便说一句,这不是32毫秒,而是320毫秒。4%的差异在我看来是巨大的。我做这些测试的全部原因