Benchmarks
Microbenchmarks are important part of DotNext library to prove than important features can speed up performance of your application or, at least, is not slowing down it.
The configuration of all benchmarks:
Parameter | Configuration |
---|---|
Host | .NET 5.0.7 (CoreCLR 5.0.721.25508, CoreFX 5.0.721.25508), X64 RyuJIT |
Job | .NET 5.0.7 (CoreCLR 5.0.721.25508, CoreFX 5.0.721.25508), X64 RyuJIT |
LaunchCount | 1 |
RunStrategy | Throughput |
OS | Ubuntu 20.04.2 |
CPU | Intel Core i7-6700HQ CPU 2.60GHz (Skylake) |
Number of CPUs | 1 |
Physical Cores | 4 |
Logical Cores | 8 |
RAM | 24 GB |
You can run benchmarks using Bench
build configuration as follows:
cd <dotnext-clone-path>/src/DotNext.Benchmarks
dotnet run -c Bench
Bitwise Equality
This benchmark compares performance of BitwiseComparer<T>.Equals with overloaded equality ==
operator. Testing data types: Guid and custom value type with multiple fields.
Method | Mean | Error | StdDev | Median |
---|---|---|---|---|
BitwiseComparer<Guid>.Equals |
4.1255 ns | 0.0350 ns | 0.0292 ns | 4.1218 ns |
Guid.Equals |
1.9494 ns | 0.0284 ns | 0.0252 ns | 1.9399 ns |
ReadOnlySpan.SequenceEqual for Guid |
5.9802 ns | 0.0306 ns | 0.0271 ns | 5.9869 ns |
BitwiseComparer<LargeStruct>.Equals |
9.8929 ns | 0.0698 ns | 0.0618 ns | 9.8828 ns |
LargeStruct.Equals |
28.8117 ns | 0.3003 ns | 0.2662 ns | 28.7669 ns |
ReadOnlySpan.SequenceEqual for LargeStruct |
10.4538 ns | 0.0659 ns | 0.0585 ns | 10.4538 ns |
Bitwise equality method has the better performance than field-by-field equality check especially for large value types because BitwiseEquals
utilizes low-level optimizations performed by .NET according with underlying hardware such as SIMD. Additionally, it uses aligned memory access in constrast to SequenceEqual method.
Equality of Arrays
This benchmark compares performance of ReadOnlySpan.SequenceEqual, OneDimensionalArray.BitwiseEquals and manual equality check between two arrays using for
loop. The benchmark is applied to the array of Guid elements.
Method | Mean | Error | StdDev |
---|---|---|---|
Guid[].BitwiseEquals , small arrays (~10 elements) |
9.196 ns | 0.0628 ns | 0.0490 ns |
ReadOnlySpan<Guid>.SequenceEqual , small arrays (~10 elements) |
37.417 ns | 0.2111 ns | 0.1872 ns |
for loop, small arrays (~10 elements) |
68.674 ns | 0.1695 ns | 0.1585 ns |
Guid[].BitwiseEquals , large arrays (~100 elements) |
66.910 ns | 1.3718 ns | 2.2920 ns |
ReadOnlySpan<Guid>.SequenceEqual , large arrays (~100 elements) |
364.899 ns | 6.3412 ns | 5.2952 ns |
for loop, large arrays (~100 elements) |
659.282 ns | 11.3921 ns | 8.8942 ns |
Bitwise equality is an absolute winner for equality check between arrays of any size.
Bitwise Hash Code
This benchmark compares performance of BitwiseComparer<T>.GetHashCode and GetHashCode
instance method for the types Guid and custom value type with multiple fields.
Method | Mean | Error | StdDev |
---|---|---|---|
Guid.GetHashCode |
1.416 ns | 0.0211 ns | 0.0198 ns |
BitwiseComparer<Guid>.GetHashCode |
6.202 ns | 0.0355 ns | 0.0315 ns |
BitwiseComparer<LargeStructure>.GetHashCode |
44.327 ns | 0.1936 ns | 0.1716 ns |
LargeStructure.GetHashCode |
20.520 ns | 0.0666 ns | 0.0623 ns |
Bitwise hash code algorithm is slower than JIT optimizations introduced by .NET 5 but still convenient in complex cases.
Bytes to Hex
This benchmark demonstrates performance of DotNext.Span.ToHex
extension method that allows to convert arbitrary set of bytes into hexadecimal form. It is compatible withSpan<T>
data type in constrast to BitConverter.ToString method.
Method | Num of Bytes | Mean | Error | StdDev |
---|---|---|---|---|
BitConverter.ToString |
16 bytes | 60.60 ns | 0.245 ns | 0.217 ns |
Span.ToHex |
16 bytes | 60.14 ns | 0.375 ns | 0.313 ns |
BitConverter.ToString |
64 bytes | 192.31 ns | 0.896 ns | 0.794 ns |
Span.ToHex |
64 bytes | 122.43 ns | 0.268 ns | 0.209 ns |
BitConverter.ToString |
128 bytes | 364.90 ns | 1.764 ns | 1.473 ns |
Span.ToHex |
128 bytes | 224.62 ns | 0.795 ns | 0.705 ns |
BitConverter.ToString |
256 bytes | 725.64 ns | 3.656 ns | 3.420 ns |
Span.ToHex |
256 bytes | 457.48 ns | 1.321 ns | 1.171 ns |
Span.ToHex
demonstrates the best performance especially for large arrays.
Fast Reflection
The next series of benchmarks demonstrate performance of strongly typed reflection provided by DotNext Reflection library.
Property Getter
This benchmark demonstrates overhead of getting instance property value caused by different mechanisms:
- Using FastMember library
- Using strongly typed reflection from DotNext Reflection library:
Type<IndexOfCalculator>.Property<int>.RequireGetter
- Using strongly typed reflection from DotNext Reflection library using special delegate type
Function<object, ValueTuple, object>
. It is assumed that instance type and property type is not known at compile type (th) so the delegate performs type check on every call. - Classic .NET reflection
Method | Mean | Error | StdDev |
---|---|---|---|
Direct call | 11.09 ns | 0.197 ns | 0.154 ns |
Reflection with DotNext using delegate type MemberGetter<IndexOfCalculator, int> |
11.88 ns | 0.155 ns | 0.145 ns |
Reflection with DotNext using DynamicInvoker |
22.40 ns | 0.251 ns | 0.222 ns |
Reflection with DotNext using delegate type Function<object, ValueTuple, object> |
22.91 ns | 0.364 ns | 0.323 ns |
ObjectAccess class from FastMember library |
47.63 ns | 0.358 ns | 0.335 ns |
.NET reflection | 163.44 ns | 2.468 ns | 2.309 ns |
Strongly typed reflection provided by DotNext Reflection library has the same performance as direct call.
Instance Method Call
This benchmark demonstrates overhead of calling instance method IndexOf
of type string caused by different mechanisms:
- Using strongly typed reflection from DotNext Reflection library:
Type<string>.Method<char, int>.Require<int>(nameof(string.IndexOf))
- Using strongly typed reflection from DotNext Reflection library using special delegate type:
Type<string>.RequireMethod<(char, int), int>(nameof(string.IndexOf));
- Using strongly typed reflection from DotNext Reflection library using special delegate type:
Function<object, (object, object), object>
. It is assumed that types of all parameters are not known at compile time. - Classic .NET reflection
The benchmark uses series of different strings to run the same set of tests. Worst case means that character lookup is performed for a string that doesn't contain the given character. Best case means that character lookup is performed for a string that has the given character.
Method | Condition | Mean | Error | StdDev |
---|---|---|---|---|
Direct call | Empty String | 5.326 ns | 0.1191 ns | 0.1783 ns |
Direct call | Best Case | 9.883 ns | 0.1057 ns | 0.0988 ns |
Direct call | Worst Case | 12.836 ns | 0.0516 ns | 0.0431 ns |
Reflection with DotNext using delegate type Func<string, char, int, int> |
Empty String | 8.619 ns | 0.1266 ns | 0.1184 ns |
Reflection with DotNext using delegate type Func<string, char, int, int> |
Best Case | 12.950 ns | 0.2557 ns | 0.3413 ns |
Reflection with DotNext using delegate type Func<string, char, int, int> |
Worst Case | 19.191 ns | 0.3604 ns | 0.4006 ns |
Reflection with DotNext using delegate type Function<string, (char, int), int> |
Empty String | 12.535 ns | 0.1385 ns | 0.1295 ns |
Reflection with DotNext using delegate type Function<string, (char, int), int> |
Best Case | 17.662 ns | 0.3306 ns | 0.3092 ns |
Reflection with DotNext using delegate type Function<string, (char, int), int> |
Worst Case | 21.126 ns | 0.3728 ns | 0.3487 ns |
Reflection with DotNext using delegate type Function<object, (object, object), object> |
Empty String | 30.211 ns | 0.1373 ns | 0.1284 ns |
Reflection with DotNext using delegate type Function<object, (object, object), object> |
Best Case | 35.754 ns | 0.0965 ns | 0.0806 ns |
Reflection with DotNext using delegate type Function<object, (object, object), object> |
Worst Case | 40.073 ns | 0.2489 ns | 0.2328 ns |
.NET reflection | Empty String | 303.852 ns | 1.9509 ns | 1.8249 ns |
.NET reflection | Best Case | 324.094 ns | 1.2919 ns | 1.1453 ns |
.NET reflection | Worst Case | 324.064 ns | 2.8673 ns | 2.6821 ns |
DotNext Reflection library offers the best result in case when delegate type exactly matches to the reflected method with small overhead measured in a few nanoseconds.
Static Method Call
This benchmark demonstrates overhead of calling static method TryParse
of type decimal caused by different mechanisms:
- Using strongly typed reflection from DotNext Reflection library:
Type<decimal>.Method.Get<TryParseDelegate>(nameof(decimal.TryParse), MethodLookup.Static)
. The delegate type exactly matches to the reflected method signature:delegate bool TryParseDelegate(string text, out decimal result)
- Using strongly typed reflection from DotNext Reflection library using special delegate type:
Function<(string text, decimal result), bool>
- Using strongly typed reflection from DotNext Reflection library using special delegate type:
Function<(object text, object result), object>
. It is assumed that types of all parameters are not known at compile time. - Classic .NET reflection
Method | Mean | Error | StdDev | Median |
---|---|---|---|---|
Direct call | 119.8 ns | 2.27 ns | 4.32 ns | 117.8 ns |
Reflection with DotNext using delegate type TryParseDelegate |
125.3 ns | 0.41 ns | 0.34 ns | 125.3 ns |
Reflection with DotNext using delegate type Function<(string text, decimal result), bool> |
131.0 ns | 0.30 ns | 0.28 ns | 131.1 ns |
Reflection with DotNext using delegate type Function<(object text, object result), object> |
147.9 ns | 0.54 ns | 0.51 ns | 147.8 ns |
.NET reflection | 530.2 ns | 1.71 ns | 1.60 ns | 530.0 ns |
Strongly typed reflection provided by DotNext Reflection library has the same performance as direct call.
Atomic Access to Arbitrary Value Type
This benchmark compares performance of Atomic<T> and Synchronized methods. The implementation of the benchmark contains concurrent read/write threads to ensure that lock contention is in place.
Method | Mean | Error | StdDev | Median |
---|---|---|---|---|
Atomic | 358.0 us | 9.36 us | 85.54 us | 345.7 us |
Synchronized | 961.7 us | 11.80 us | 105.92 us | 946.1 us |
SpinLock | 1,586.0 us | 45.64 us | 424.80 us | 1,586.0 us |
File-buffering Writer
This benchmark compares performance of FileBufferingWriteStream from ASP.NET Core and FileBufferingWriter from .NEXT library.
Both classes switching from in-memory buffer to file-based buffer during benchmark execution. Note that benchmark result highly depends on disk I/O performance. The following results were obtained using NVMe SSD.
Method | Mean | Error | StdDev | Median |
---|---|---|---|---|
FileBufferingWriter in synchronous mode |
950.8 us | 8.58 us | 7.61 us | |
FileBufferingWriteStream in synchronous mode |
14,295.1 us | 838.55 us | 2,351.37 us | |
FileBufferingWriter in asynchronous mode |
7,825.6 us | 337.13 us | 894.03 us | |
FileBufferingWriteStream in asynchronous mode |
18,418.7 us | 993.00 us | 2,896.64 us |
FileBufferingWriter
is a winner in synchronous scenario because it has native support for synchronous mode in contrast to FileBufferingWriteStream
.
Various Buffer Types
This benchmark demonstrates the performance of write operation and memory consumption of the following types:
- MemoryStream
- RecyclableMemoryStream
- SparseBufferWriter<byte>
- PooledArrayBufferWriter<byte>
- FileBufferingWriter
Buffer Type | Written bytes | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|---|---|---|
MemoryStream |
100 | 66.91 ns | 1.379 ns | 2.187 ns | 1.00 | 0.00 | 0.1097 | - | - | 344 B |
PooledArrayBufferWriter<byte> |
100 | 237.78 ns | 1.819 ns | 1.519 ns | 3.53 | 0.10 | 0.0405 | - | - | 128 B |
SparseBufferWriter<byte> |
100 | 249.18 ns | 3.458 ns | 3.234 ns | 3.68 | 0.14 | 0.0663 | - | - | 208 B |
FileBufferingWriter |
100 | 1,681.56 ns | 31.827 ns | 29.771 ns | 24.85 | 0.94 | 0.0324 | - | - | 104 B |
RecyclableMemoryStream |
100 | 2,503.46 ns | 10.280 ns | 9.113 ns | 37.16 | 0.93 | 0.1030 | - | - | 328 B |
MemoryStream |
1000 | 124.90 ns | 0.882 ns | 1.264 ns | 1.00 | 0.00 | 0.3467 | - | - | 1088 B |
SparseBufferWriter<byte> |
1000 | 273.03 ns | 2.024 ns | 1.794 ns | 2.20 | 0.03 | 0.0663 | - | - | 208 B |
PooledArrayBufferWriter<byte> |
1000 | 491.45 ns | 2.027 ns | 1.693 ns | 3.96 | 0.04 | 0.0401 | - | - | 128 B |
FileBufferingWriter |
1000 | 1,704.61 ns | 33.291 ns | 31.141 ns | 13.72 | 0.39 | 0.0324 | - | - | 104 B |
RecyclableMemoryStream |
1000 | 2,535.13 ns | 20.769 ns | 19.427 ns | 20.40 | 0.21 | 0.1030 | - | - | 328 B |
SparseBufferWriter<byte> |
10000 | 778.99 ns | 3.440 ns | 3.050 ns | 0.32 | 0.00 | 0.0858 | - | - | 272 B |
PooledArrayBufferWriter<byte> |
10000 | 1,570.87 ns | 11.332 ns | 10.045 ns | 0.64 | 0.01 | 0.0401 | - | - | 128 B |
MemoryStream |
10000 | 2,441.00 ns | 43.130 ns | 38.234 ns | 1.00 | 0.00 | 9.8343 | - | - | 30880 B |
FileBufferingWriter |
10000 | 2,841.10 ns | 54.135 ns | 57.924 ns | 1.17 | 0.02 | 0.0305 | - | - | 104 B |
RecyclableMemoryStream |
10000 | 2,971.70 ns | 23.778 ns | 22.242 ns | 1.22 | 0.02 | 0.1030 | - | - | 328 B |
SparseBufferWriter<byte> |
100000 | 5,332.73 ns | 18.125 ns | 16.954 ns | 0.08 | 0.00 | 0.1450 | - | - | 464 B |
RecyclableMemoryStream |
100000 | 7,589.19 ns | 37.040 ns | 32.835 ns | 0.11 | 0.00 | 0.0916 | - | - | 328 B |
PooledArrayBufferWriter<byte> |
100000 | 8,901.54 ns | 34.404 ns | 28.729 ns | 0.13 | 0.00 | 0.0305 | - | - | 128 B |
MemoryStream |
100000 | 66,530.32 ns | 1,048.177 ns | 980.465 ns | 1.00 | 0.00 | 41.6260 | 41.6260 | 41.6260 | 260340 B |
FileBufferingWriter |
100000 | 117,812.86 ns | 2,269.839 ns | 3,533.865 ns | 1.79 | 0.06 | - | - | - | 368 B |
SparseBufferWriter<byte> |
1000000 | 50,656.44 ns | 906.284 ns | 847.739 ns | 0.05 | 0.00 | 0.1831 | - | - | 656 B |
RecyclableMemoryStream |
1000000 | 53,671.61 ns | 854.939 ns | 799.711 ns | 0.06 | 0.00 | 0.1831 | - | - | 736 B |
PooledArrayBufferWriter<byte> |
1000000 | 84,345.21 ns | 1,475.370 ns | 1,699.039 ns | 0.09 | 0.00 | - | - | - | 128 B |
FileBufferingWriter |
1000000 | 739,081.13 ns | 14,452.230 ns | 17,204.351 ns | 0.79 | 0.02 | - | - | - | 368 B |
MemoryStream |
1000000 | 931,331.53 ns | 17,399.875 ns | 14,529.684 ns | 1.00 | 0.00 | 498.0469 | 498.0469 | 498.0469 | 2095552 B |