NetFabric/NetFabric.Hyperlinq

make EmptyEnumerable a singleton

Meir017 opened this issue · 5 comments

You're right when using reference types. This is a value type so it would be copied anyway, when not using ref. I wrote an article where I evaluate all possible implementations of Empty I could come up with: https://medium.com/@antao.almada/performance-of-value-type-vs-reference-type-enumerators-820ab1acc291
In the article I do recognize that the singleton implementation is better when using IEnumerable<T> but in this project I want to use value types as much as possible: https://medium.com/@antao.almada/netfabric-hyperlinq-optimizing-linq-348e02566cef
Thanks for the feedback!

This benchmark shows that the non-singleton value type implementation has zero-allocation and can perform much better than System.Linq.Enumerable.Empty<T>().

BenchmarkDotNet=v0.11.3, OS=Windows 10.0.17763.292 (1809/October2018Update/Redstone5)
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=2.1.600-preview-009472
  [Host]     : .NET Core 2.1.7 (CoreCLR 4.6.27129.04, CoreFX 4.6.27129.04), 64bit RyuJIT
  DefaultJob : .NET Core 2.1.7 (CoreCLR 4.6.27129.04, CoreFX 4.6.27129.04), 64bit RyuJIT

Method Categories Mean Error StdDev Median Ratio Gen 0/1k Op Gen 1/1k Op Gen 2/1k Op Allocated Memory/Op
Linq_Empty_ForEach Empty() 11.9732 ns 0.0798 ns 0.0747 ns 11.9627 ns 1.000 - - - -
Hyperlinq_Empty_ForEach Empty() 0.0013 ns 0.0027 ns 0.0025 ns 0.0000 ns 0.000 - - - -
Hyperlinq_Empty_For Empty() 0.2649 ns 0.0273 ns 0.0255 ns 0.2501 ns 0.022 - - - -
Linq_Empty_Count Empty().Count() 17.9902 ns 0.3772 ns 0.3705 ns 17.8045 ns 1.000 - - - -
Hyperlinq_Empty_Count Empty().Count() 0.0000 ns 0.0000 ns 0.0000 ns 0.0000 ns 0.000 - - - -
Linq_Empty_Select_ForEach Empty().Select() 32.4302 ns 0.1482 ns 0.1314 ns 32.4508 ns 1.00 - - - -
Hyperlinq_Empty_Select_ForEach Empty().Select() 1.5091 ns 0.0118 ns 0.0105 ns 1.5100 ns 0.05 - - - -
Hyperlinq_Empty_Select_For Empty().Select() 9.9025 ns 0.1389 ns 0.1232 ns 9.9451 ns 0.31 - - - -
Linq_Empty_Where_ForEach Empty().Where() 19.6587 ns 0.4065 ns 0.3604 ns 19.6749 ns 1.00 - - - -
Hyperlinq_Empty_Where_ForEach Empty().Where() 1.7173 ns 0.0085 ns 0.0076 ns 1.7196 ns 0.09 - - - -
Linq_Empty_Where_Select_ForEach Empty().Where().Select() 36.1020 ns 0.7234 ns 0.6767 ns 36.1354 ns 1.00 - - - -
Hyperlinq_Empty_Where_Select_ForEach Empty().Where().Select() 2.2709 ns 0.0698 ns 0.0653 ns 2.2433 ns 0.06 - - - -

hmm, so does it make sense to open a pull-request in the corefx repo?

Their version of Empty() makes sense for their implementation of LINQ. My implementation depends on the use of interface constraints and the interface IValueEnumerable so that it's not boxed. It only makes sense when adopting it all together.

Closing this issue but more feedback is welcome!