UPDATED: Oct 13th, 2022
nawfal's answer benchmarked
Benchmarked in NET6.0, was just seeing how necessary this was still.
Added Activator.CreateInstance<T>();
test and struct
tests.
Activator1 = new();
Activator2 = Activator.CreateInstance<T>();
Activator3 = New<T>.Instance();
TL;DR:
Still recommended for simple classes. Don't use for structs.
using BenchmarkDotNet.Running;
using InstanceBenchmark;
//BenchmarkRunner.Run<ActivatorBenchmark<TestClass>>();
BenchmarkRunner.Run<ActivatorBenchmark<TestStruct>>();
public class TestClass
{
public string Name { get; set; }
public int Id { get; set; }
public string Email { get; set; }
}
public struct TestStruct
{
public string Name { get; set; }
public int Id { get; set; }
public string Email { get; set; }
}
[MemoryDiagnoser]
[SimpleJob(runtimeMoniker: RuntimeMoniker.Net60)]
[GenericTypeArguments(typeof(TestClass))]
[GenericTypeArguments(typeof(TestStruct))]
public class ActivatorBenchmark<T> where T : new()
{
[Benchmark(Baseline = true)]
[Arguments(1_000)]
[Arguments(1_000_000)]
[Arguments(100_000_000)]
public void ActivatorTest1(int x)
{
for (int i = 0; i < x; i++)
{
var t = new T();
}
}
[Benchmark]
[Arguments(1_000)]
[Arguments(1_000_000)]
[Arguments(100_000_000)]
public void ActivatorTest2(int x)
{
for (int i = 0; i < x; i++)
{
var t = Activator.CreateInstance<T>();
}
}
[Benchmark]
[Arguments(1_000)]
[Arguments(1_000_000)]
[Arguments(100_000_000)]
public void ActivatorTest3(int x)
{
for (int i = 0; i < x; i++)
{
var t = New<T>.Instance();
}
}
}
public static class TestHelpers
{
public static class New<T>
{
public static readonly Func<T> Instance = Creator();
private static Func<T> Creator()
{
Type t = typeof(T);
if (t == typeof(string))
{ return Expression.Lambda<Func<T>>(Expression.Constant(string.Empty)).Compile(); }
if (t.HasDefaultConstructor())
{ return Expression.Lambda<Func<T>>(Expression.New(t)).Compile(); }
return () => (T)FormatterServices.GetUninitializedObject(t);
}
}
public static bool HasDefaultConstructor(this Type t)
{
return t.IsValueType || t.GetConstructor(Type.EmptyTypes) != null;
}
}
Class Results
// * Summary *
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.1098/21H2)
Intel Core i9-10900KF CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK=6.0.402
[Host] : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
Job=.NET 6.0 Runtime=.NET 6.0
| Method | x | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio |
|--------------- |---------- |---------------:|--------------:|--------------:|------:|--------:|------------:|--------------:|------------:|
| ActivatorTest1 | 1000 | 9.946 μs | 0.1927 μs | 0.2142 μs | 1.00 | 0.00 | 3.8147 | 39.06 KB | 1.00 |
| ActivatorTest2 | 1000 | 9.808 μs | 0.0721 μs | 0.0674 μs | 0.98 | 0.02 | 3.8147 | 39.06 KB | 1.00 |
| ActivatorTest3 | 1000 | 6.219 μs | 0.1199 μs | 0.1427 μs | 0.63 | 0.02 | 3.8223 | 39.06 KB | 1.00 |
| | | | | | | | | | |
| ActivatorTest1 | 1000000 | 9,834.625 μs | 31.8609 μs | 26.6053 μs | 1.00 | 0.00 | 3812.5000 | 39063.26 KB | 1.00 |
| ActivatorTest2 | 1000000 | 10,671.712 μs | 47.0675 μs | 44.0269 μs | 1.09 | 0.01 | 3812.5000 | 39063.26 KB | 1.00 |
| ActivatorTest3 | 1000000 | 6,295.779 μs | 121.9964 μs | 186.3014 μs | 0.65 | 0.03 | 3820.3125 | 39062.5 KB | 1.00 |
| | | | | | | | | | |
| ActivatorTest1 | 100000000 | 995,902.729 μs | 7,355.4492 μs | 6,520.4141 μs | 1.00 | 0.00 | 382000.0000 | 3906325.27 KB | 1.00 |
| ActivatorTest2 | 100000000 | 982,209.783 μs | 6,630.1000 μs | 5,176.3460 μs | 0.99 | 0.01 | 382000.0000 | 3906335.95 KB | 1.00 |
| ActivatorTest3 | 100000000 | 618,402.807 μs | 4,305.6817 μs | 4,027.5373 μs | 0.62 | 0.01 | 382000.0000 | 3906253.48 KB | 1.00 |
Struct Results
// * Summary *
BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.1098/21H2)
Intel Core i9-10900KF CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK=6.0.402
[Host] : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
Job=.NET 6.0 Runtime=.NET 6.0
| Method | x | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio |
|--------------- |---------- |-----------------:|--------------:|--------------:|------:|--------:|----------:|------------:|
| ActivatorTest1 | 1000 | 212.8 ns | 4.27 ns | 4.38 ns | 1.00 | 0.00 | - | NA |
| ActivatorTest2 | 1000 | 209.5 ns | 0.10 ns | 0.09 ns | 0.98 | 0.02 | - | NA |
| ActivatorTest3 | 1000 | 1,646.0 ns | 2.69 ns | 2.10 ns | 7.77 | 0.14 | - | NA |
| | | | | | | | | |
| ActivatorTest1 | 1000000 | 204,577.8 ns | 128.30 ns | 107.14 ns | 1.00 | 0.00 | - | NA |
| ActivatorTest2 | 1000000 | 204,569.4 ns | 116.38 ns | 108.86 ns | 1.00 | 0.00 | - | NA |
| ActivatorTest3 | 1000000 | 1,644,446.5 ns | 12,606.12 ns | 9,842.03 ns | 8.04 | 0.05 | 1 B | NA |
| | | | | | | | | |
| ActivatorTest1 | 100000000 | 20,455,141.5 ns | 12,934.68 ns | 12,099.11 ns | 1.00 | 0.00 | 15 B | 1.00 |
| ActivatorTest2 | 100000000 | 20,460,807.6 ns | 25,571.37 ns | 19,964.44 ns | 1.00 | 0.00 | 15 B | 1.00 |
| ActivatorTest3 | 100000000 | 164,105,645.0 ns | 327,107.27 ns | 305,976.34 ns | 8.02 | 0.01 | 898 B | 59.87 |
CreateInstance
- because the whole point of that is that you'd callCreateInstance
once but then remember the factory delegate. If you're callingCreateInstance
on each operation, then yes, it'll be slower... – OldfangledDelegate.CreateDelegate
onActivator.CreateInstance
or just storing aFunc<>
which directly callsCreateInstance
? – SlifkaDelegate.CreateDelegate
, so that you don't need to create a new DynamicMethod each time. – OldfangledDelegate.CreateDelegate
andActivator.CreateInstance
together. – Slifka