From 0b88041afd4157eeb2eed9003654801a70fbfbf6 Mon Sep 17 00:00:00 2001 From: Boring3 Date: Mon, 30 Nov 2020 19:27:10 +0800 Subject: [PATCH] Update README.md --- README.md | 141 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 117 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 0991210..a536f78 100644 --- a/README.md +++ b/README.md @@ -3,21 +3,37 @@ FastGenericNew is 10x times faster than `Activator.CreateInstance()` / `new T()` +## Navigation + - [Install](#install) + - [DotNet CLI](#dotnet-cli) + - [Package Reference](#package-reference) + - [Package Manager](#package-manager) + - [Features](#features) + - [Examples](#examples) + - [Fast create instance of `T`](#fast-create-instance-of-t) + - [Fast create instance of `T` with parameter(s)](#fast-create-instance-of-t-with-parameters) + - [Fast create instance by using TypeNew **(Experimental)**](#fast-create-instance-by-using-typenew-**Experimental**) + - [Benchmark](#benchmark) + - [Environment](#environment) + - [Reference Type (class)](#reference-type-class) + - [Value Type (struct)](#value-type-struct) + - [Legends](#legends) + - [How it works](#how-it-works) ## Install ### DotNet CLI ```powershell -dotnet add package Boring3.FastGenericNew --version 1.1.0 +dotnet add package Boring3.FastGenericNew --version 2.0.0 ``` ### Package Reference ```xml - + ``` ### Package Manager ```powershell -Install-Package Boring3.FastGenericNew -Version 1.1.0 +Install-Package Boring3.FastGenericNew -Version 2.0.0 ``` ## Features @@ -26,27 +42,44 @@ Install-Package Boring3.FastGenericNew -Version 1.1.0 - Non-Public Constructor Supported - Zero box/unbox - ValueType Supported + - Source Generator + - Fast Non-Generic TypeNew Support **(Experimental)** ## Examples -Fast create instance of `T`: +### Fast create instance of `T` ```cs FastNew.CreateInstance(); ``` -Fast create instance of `T` with parameter(s): +### Fast create instance of `T` with parameter(s) ```cs FastNew.CreateInstance("parameter"); FastNew.CreateInstance("parameter", 0); ``` +### Fast create instance by using TypeNew: **(Experimental)** + +```cs +// Slower a bit than FastNew because boxing/unboxing. +// But still much faster than Activator.CreateInstance(typeof(Example)) +Func objectResult = TypeNew.GetCreateInstance(typeof(Example)); + +object result = objectResult(); + +// Performance equals FastNew. Zero box/unbox +Func genericResult = TypeNew.GetCreateInstance(typeof(Example)); +Func withGenericParameters = TypeNew.GetCreateInstance(typeof(Example), typeof(string)); + +Example result2 = genericResult("parameter"); +``` + ## Benchmark ### **Environment** ``` ini - BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores .NET Core SDK=5.0.100 @@ -55,24 +88,41 @@ AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores ``` ### **Reference Type** (class) -| Method | Mean | Error | StdDev | Ratio | RatioSD | Baseline | Gen 0 | Gen 1 | Gen 2 | Allocated | Code Size | -|---------------- |----------:|----------:|----------:|------:|--------:|--------- |-------:|------:|------:|----------:|----------:| -| DirectNew | 1.757 ns | 0.0083 ns | 0.0074 ns | 0.79 | 0.01 | No | 0.0029 | - | - | 24 B | 25 B | -| FastNewT | 2.223 ns | 0.0121 ns | 0.0108 ns | 1.00 | 0.00 | Yes | 0.0029 | - | - | 24 B | 24 B | -| ActivatorCreate | 32.653 ns | 0.4308 ns | 0.4030 ns | 14.69 | 0.18 | No | 0.0029 | - | - | 24 B | 88 B | -| NewT | 32.717 ns | 0.6927 ns | 0.7977 ns | 14.70 | 0.37 | No | 0.0029 | - | - | 24 B | 88 B | +| Method | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Baseline | Gen 0 | Gen 1 | Gen 2 | Allocated | +|--------------------- |-------------- |----------:|----------:|----------:|------:|--------:|--------- |-------:|------:|------:|----------:| +| FastNewT | .NET 4.8 | 8.400 ns | 0.1189 ns | 0.1112 ns | 1.00 | 0.00 | Yes | 0.0145 | - | - | 24 B | +| DirectNew | .NET 4.8 | 1.741 ns | 0.0902 ns | 0.1483 ns | 0.21 | 0.02 | No | 0.0145 | - | - | 24 B | +| ActivatorCreate | .NET 4.8 | 57.979 ns | 1.1526 ns | 1.2332 ns | 6.89 | 0.19 | No | 0.0144 | - | - | 24 B | +| TypeNewGenericResult | .NET 4.8 | 8.389 ns | 0.0864 ns | 0.0722 ns | 1.00 | 0.02 | No | 0.0145 | - | - | 24 B | +| TypeNewObjectResult | .NET 4.8 | 8.093 ns | 0.0420 ns | 0.0393 ns | 0.96 | 0.01 | No | 0.0145 | - | - | 24 B | +| | | | | | | | | | | | | +| FastNewT | .NET Core 5.0 | 2.234 ns | 0.0108 ns | 0.0090 ns | 1.00 | 0.00 | Yes | 0.0029 | - | - | 24 B | +| DirectNew | .NET Core 5.0 | 2.103 ns | 0.0452 ns | 0.0377 ns | 0.94 | 0.02 | No | 0.0029 | - | - | 24 B | +| ActivatorCreate | .NET Core 5.0 | 32.277 ns | 0.5308 ns | 0.4965 ns | 14.43 | 0.22 | No | 0.0029 | - | - | 24 B | +| TypeNewGenericResult | .NET Core 5.0 | 2.309 ns | 0.0388 ns | 0.0344 ns | 1.03 | 0.02 | No | 0.0029 | - | - | 24 B | +| TypeNewObjectResult | .NET Core 5.0 | 2.495 ns | 0.0271 ns | 0.0253 ns | 1.12 | 0.01 | No | 0.0029 | - | - | 24 B | ### **Value Type** (struct) -| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Baseline | Gen 0 | Gen 1 | Gen 2 | Allocated | Code Size | -|---------------- |-----------:|----------:|----------:|-----------:|-------:|--------:|--------- |-------:|------:|------:|----------:|----------:| -| DirectNew | 0.0014 ns | 0.0011 ns | 0.0010 ns | 0.0014 ns | 0.004 | 0.00 | No | - | - | - | - | 3 B | -| NewT | 0.0102 ns | 0.0130 ns | 0.0121 ns | 0.0023 ns | 0.020 | 0.03 | No | - | - | - | - | 3 B | -| FastNewT | 0.4629 ns | 0.0099 ns | 0.0077 ns | 0.4613 ns | 1.000 | 0.00 | Yes | - | - | - | - | 24 B | -| ActivatorCreate | 33.6680 ns | 0.7328 ns | 0.6855 ns | 33.7002 ns | 72.346 | 1.36 | No | 0.0029 | - | - | 24 B | 88 B | - -> **Note:** JIT have two solutions for `new T()` compilation. -> For Reference Types. `new T()` will equals `Activator.CreateInstance()` -> For Value Types. `new T()` will allocate it inline. So it fast than `FastNew` that unable to be inlined. +| Method | Runtime | Mean | Error | StdDev | Median | Ratio | RatioSD | Baseline | Gen 0 | Gen 1 | Gen 2 | Allocated | +|--------------------- |-------------- |-----------:|----------:|----------:|-----------:|------:|--------:|--------- |-------:|------:|------:|----------:| +| FastNewT | .NET 4.8 | 0.5039 ns | 0.0087 ns | 0.0082 ns | 0.5002 ns | 1.00 | 0.00 | Yes | - | - | - | - | +| DirectNew | .NET 4.8 | 0.1765 ns | 0.0035 ns | 0.0029 ns | 0.1767 ns | 0.35 | 0.01 | No | - | - | - | - | +| ActivatorCreate | .NET 4.8 | 49.7076 ns | 0.5928 ns | 0.5545 ns | 49.8084 ns | 98.68 | 1.99 | No | 0.0145 | - | - | 24 B | +| TypeNewGenericResult | .NET 4.8 | 0.6239 ns | 0.0048 ns | 0.0038 ns | 0.6226 ns | 1.23 | 0.02 | No | - | - | - | - | +| TypeNewObjectResult | .NET 4.8 | 8.4254 ns | 0.0709 ns | 0.0629 ns | 8.4143 ns | 16.71 | 0.29 | No | 0.0145 | - | - | 24 B | +| | | | | | | | | | | | | | +| FastNewT | .NET Core 5.0 | 0.4657 ns | 0.0012 ns | 0.0011 ns | 0.4652 ns | 1.000 | 0.00 | Yes | - | - | - | - | +| DirectNew | .NET Core 5.0 | 0.0023 ns | 0.0040 ns | 0.0037 ns | 0.0004 ns | 0.005 | 0.01 | No | - | - | - | - | +| ActivatorCreate | .NET Core 5.0 | 0.0123 ns | 0.0031 ns | 0.0028 ns | 0.0120 ns | 0.026 | 0.01 | No | - | - | - | - | +| TypeNewGenericResult | .NET Core 5.0 | 0.4571 ns | 0.0053 ns | 0.0047 ns | 0.4564 ns | 0.982 | 0.01 | No | - | - | - | - | +| TypeNewObjectResult | .NET Core 5.0 | 2.2395 ns | 0.0115 ns | 0.0102 ns | 2.2374 ns | 4.811 | 0.02 | No | 0.0029 | - | - | 24 B | + +### Notes +> `new T()` will compile to `Activator.CreateInstance()` + +> JIT have two solutions for `Activator.CreateInstance()` compilation. +> For Reference Types. `Activator.CreateInstance()` slow as everyone knows. +> For Value Types. `Activator.CreateInstance()` will allocate it inline. So it fast than `FastNew` that unable to be inlined. ### **Legends** @@ -91,10 +141,53 @@ AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores ## How it works -Not like `Activator.CreateInstance()`. FastGenericNew will dynamically compile a method that return `T`. And cache it up by generic. +Not like `Activator.CreateInstance()`. FastGenericNew will dynamically compile a method that return a real `new T()`. And cache it up by generic. You can invoke this method by a delegate with no any box/unbox. But there's still a little problem anyway. .NET Runtime will not inline delegate in any case currently. -So it causes bit more costs than direct new. \ No newline at end of file +So it causes bit more costs than direct new. + +```cs +public static class FastNew +{ + public static readonly Expression> SourceExpression = + !typeof(T).IsValueType + ? Expression.Lambda>(Expression.New(ConstructorOf.value), Array.Empty()) + : Expression.Lambda>(Expression.New(typeof(T)), Array.Empty()); + + public static readonly Func CreateInstance = SourceExpression.Compile(); +} +``` + +```cs +public static class TypeNew +{ + public static Func GetCreateInstance(Type type) => + !type.IsValueType + ? (Func) + typeof(FastNew<>) + .MakeGenericType(type) + .GetField("CreateInstance") + .GetValue(null) + : () => default(T); + + public static Func GetCreateInstance(Type type) + { + if(!type.IsValueType) + { + return (Func) + typeof(FastNew<>) + .MakeGenericType(type) + .GetField("CreateInstance") + .GetValue(null); + } + if (createInstanceCaches.TryGetValue(type, out var result)) + return result; + createInstanceCaches.Add(type, + result = Expression.Lambda>(Expression.Convert(Expression.New(type), typeof(object))).Compile()); + return result; + } +} +``` \ No newline at end of file