Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is using a Func<> so much faster than using the new() constraint on a generic sequence creator

Consider the following code...

In my tests for a RELEASE (not debug!) x86 build on a Windows 7 x64 PC (Intel i7 3GHz) I obtained the following results:

CreateSequence() with new() took 00:00:00.9158071
CreateSequence() with creator() took 00:00:00.1383482

CreateSequence() with new() took 00:00:00.9198317
CreateSequence() with creator() took 00:00:00.1372920

CreateSequence() with new() took 00:00:00.9340462
CreateSequence() with creator() took 00:00:00.1447375

CreateSequence() with new() took 00:00:00.9344077
CreateSequence() with creator() took 00:00:00.1365162

It seems that using a Func<> to define a delegate to create new objects is more than 6 times faster than calling "new T()" directly.

I find this slightly unexpected... I guess it's because of some inlining done by the Jitter, but I'd have thought that it would have been able to optimize the "new T()" just as well.

Does anyone have an explanation for this?

Maybe I'm making some mistake. (I've considered the effect the garbage collector might have, but rearranging the code and adding GC.Collect() and so on doesn't change the results significantly).

Anyway, here's the code:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();

            int repeats =    100;
            int count   = 100000;

            for (int outer = 0; outer < 4; ++outer)
            {
                sw.Restart();

                for (int inner = 0; inner < repeats; ++inner)
                {
                    CreateSequence<object>(count).Count();
                }

                Console.WriteLine("CreateSequence() with new() took " + sw.Elapsed);
                sw.Restart();

                for (int inner = 0; inner < repeats; ++inner)
                {
                    CreateSequence(count, () => new object()).Count();
                }

                Console.WriteLine("CreateSequence() with creator() took " + sw.Elapsed);
                Console.WriteLine();
            }
        }

        public static IEnumerable<T> CreateSequence<T>(int n) where T: new()
        {
            for (int i = 0; i < n; ++i)
            {
                yield return new T();
            }
        }

        public static IEnumerable<T> CreateSequence<T>(int n, Func<T> creator)
        {
            for (int i = 0; i < n; ++i)
            {
                yield return creator();
            }
        }
    }
}
like image 864
Matthew Watson Avatar asked Apr 16 '12 12:04

Matthew Watson


1 Answers

The new() constraint only ensures that the type passed in has a parameterless constructor. If you actually call new T() (or whatever your type argument's name is), it actually does this:

Activator.CreateInstance<T>();

Which, at its core, uses reflection.

like image 184
Adam Robinson Avatar answered Nov 02 '22 05:11

Adam Robinson