Use FrozenDictionary for OperatorInfo table#327
Use FrozenDictionary for OperatorInfo table#327BCSharp wants to merge 2 commits intoIronLanguages:mainfrom
Conversation
| /// </summary> | ||
| internal sealed class OperatorInfo { | ||
| private static readonly Dictionary<ExpressionType, OperatorInfo> _infos = MakeOperatorTable(); // table of ExpressionType, names, and alt names for looking up methods. | ||
| private static readonly OperatorDictionary _infos = MakeOperatorTable(); // table of ExpressionType, names, and alt names for looking up methods. |
There was a problem hiding this comment.
Is there an advantage to having OperatorDictionary here instead of something like IReadOnlyDictionary<ExpressionType, OperatorInfo>? If not we could eliminate OperatorDictionary and simplify a bit.
There was a problem hiding this comment.
A call on a concrete type is faster than calling through an interface. For .NET 8+ the compiler may apply some tricks to speed up the interface call, but I am not sure when they kick in.
| #if NET10_0_OR_GREATER | ||
| ReadOnlySpan<KeyValuePair<ExpressionType, OperatorInfo>> data = [ | ||
| #elif NET8_0_OR_GREATER | ||
| var data = new KeyValuePair<ExpressionType, OperatorInfo>[] { |
There was a problem hiding this comment.
Maybe a bit nicer?
KeyValuePair<ExpressionType, OperatorInfo>[] data = [There was a problem hiding this comment.
It is nicer but not the same. It creates a temporary array on the heap. The .NET 10 version creates a span on the stack; no heap pressure,
Unless you refer only to line 73 — fine with me. I simply have a habit of putting the type on the RHS if possible. If you prefer to put it before the variable name (instead of var) I would do it also for the .NET Framework version, for consistency.
There was a problem hiding this comment.
Yeah I was refering to just line 73. I guess I prefer the look of the new square bracket syntax to the curly brackets for building an array.
|
I am marking this PR as draft. Some benchmarks on .NET 10 show |
|
I guess since the point of this PR is performance, how does it compare to a plain array lookup with bounds check (cast the enum to int)? |
|
Benchmarks published on the internet on .NET 8 show Now I need to verify the actual speed of reads, and compare them to `Dictionary reads and an array lookup. |
From the documentation:
This perfectly describes the case of
OperatorInfo. Unfortunately,FrozenDictionaryexists only in .NET 8.0 and above. It does not exist in .NET Framework, unless an external NuGet package is added as dependency and adding dependencies is not something I would do for a localized performance gain.Furthermore, the really efficient (both in terms of performance as well as in heap stress) way of creating it is available only since .NET 10.0 (actually 9.0, but the syntax is easier since 10.0). This creates three ways how the lookup of
OperatorInfois performed, and it is reflected in the new code.For .NET 10.0 and up:
It uses
FrozenDictionaryinitialized from a blob on the stack; no temporary heap objects that have to be garbage-collected.For .NET 8.0 up to .NET 10
It uses
FrozenDictionaryinitialized from an array of key-value pairs constructed on the heap. It provides all performance benefits ofFrozenDictionaryat the cost of one temporary object on the heap and of significant size.For .NET Framework (and .NET Standard 2.0 I suppose)
It uses generic
Dictionary, just like the old code, no unnecessary objects on the heap. The construction is the fastest (which happens only once at startup) but the lookup is significantly slower.There are some methods that do simple linear search by string of the operator table, but they all are on the obsolete path. I have added
ObsoleteAttributeto make it more visible.