How will a single-field structure of an object be faster than a raw object?

I have a struct that contains one object field to make working with an object easier. I wanted to test the performance (I expected some deterioration), but I get very amazing results. The struct version is actually faster:

Without box: 8.08 s

With field: 7.76 s

How is this possible?

Below is the complete test code for reproducing the results.

 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 { } } 

EDIT:

I modified the IsDouble and IsObject tags to have the same statements as another test. I ran the application again and the resulting times are the same.

EDIT2:

This code was tested using Release compilation with 32-bit without an attached debugger; .NET 4.5 and Visual Studio 2012. Compiling with a 64-bit version gives completely different results; on my car:

Without box: 8.23 ​​s

With field: 16.99 s

+7
performance c #
source share
1 answer

I copied the exact code, ran its Release without a debugger (both important!) And on x64. Results:

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

Test change to:

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

Reduces lead time:

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

Why? Because JIT decides not to embed IsDouble , but manual embedding helps. This is strange because it is such a small function. call on line 13 is the call.

enter image description here

Now why is there still a difference in performance? .NET JIT is not the best compiler out there ... maybe some instructions are a little different. You can find out by comparing the disassembly of the two versions. I will not have time for this, because I expect that the difference will be completely uninteresting.

I would expect the C compiler to get this right. The structure should behave as a separate element of the object that it contains. Small methods should be built in. This is definitely doable with today's compiler technology. Let's hope that the next generation of JIT and NGEN can do this. A new JIT (RyuJIT) is being developed and they are promoting optimization from the VC backend to NGEN (recently announced).

+4
source share

All Articles