Skip to content

Improve Enum function performance (TryParse, ToString, Parse, etc...) #79762

@tkreindler

Description

@tkreindler

Description

Although the Enum code performance has been greatly improved throughout .NET Core and a lot of the worst offenders (HasFlag) have been improved there are still clearly performance gains to be had. In our MSInternal service we still use a home brewed EnumHelper class for our frequent use of TryParse and ToString. Although performance on the default implementations has improved in recent .NET versions it's still significantly less performant as our home brewed solution and even slower compared to some widely available libraries I've also tested FastEnum and Enums.NET. The downside of including such libraries are the same as always, increasing complexity of dependency graphs and the question of ownership and maintainability.

Configuration

Production scenario:
net6.0
Windows Server Evergreen
x64
Various

Benchmarking scenario:
net6.0 and net7.0 as labeled
Windows 11 22H2
x64
i7 8700k

Regression?

No

Data

These charts contain four different paths, the default .NET functions, our homebrewed solution that involves a little caching on top of the default implementation, and then the paths of two libraries FastEnum and Enums.NET. The Parse methods include two tests for each, one where a string is provided and one where only a ReadOnlySpan is provided so implementations that can work well with spans are preferred.

ToString net6.0

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Default 5,772.3 us 115.05 us 127.88 us 585.9375 203.1250 203.1250 3125.82 KB
EnumHelper 679.0 us 6.74 us 6.30 us 179.6875 179.6875 179.6875 781.42 KB
FastEnum 633.7 us 8.35 us 7.41 us 181.6406 181.6406 181.6406 781.39 KB
EnumsDotnet 664.7 us 9.78 us 9.14 us 181.6406 181.6406 181.6406 781.38 KB

ToString net7.0

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
Default 4,859.5 us 86.12 us 199.59 us 562.5000 187.5000 187.5000 3125.69 KB
EnumHelper 736.1 us 7.41 us 6.93 us 179.6875 179.6875 179.6875 781.4 KB
FastEnum 747.4 us 14.82 us 27.48 us 181.6406 181.6406 181.6406 781.42 KB
EnumsDotnet 779.1 us 14.32 us 20.54 us 181.6406 181.6406 181.6406 781.4 KB

Parse net6.0

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
.NET Default Parse(String) 7.005 ms 0.1261 ms 0.2071 ms 78.1250 78.1250 78.1250 390.81 KB
Home brewed Parse(String) 3.833 ms 0.0741 ms 0.0854 ms 101.5625 101.5625 101.5625 390.73 KB
FastEnum Parse(String) 2.724 ms 0.0542 ms 0.0580 ms 101.5625 101.5625 101.5625 390.73 KB
Enums.NET Parse(String) 3.073 ms 0.0356 ms 0.0333 ms 101.5625 101.5625 101.5625 390.73 KB
.NET Default Parse(Span) 6.840 ms 0.0797 ms 0.0745 ms 93.7500 93.7500 93.7500 390.81 KB
Home brewed Parse(Span.ToString()) 4.129 ms 0.0821 ms 0.1124 ms 93.7500 93.7500 93.7500 390.73 KB
FastEnum Parse(Span.ToString()) 3.036 ms 0.0596 ms 0.0586 ms 101.5625 101.5625 101.5625 390.73 KB
Enums.NET Parse(Span.ToString()) 3.109 ms 0.0482 ms 0.0451 ms 101.5625 101.5625 101.5625 390.76 KB

Parse net7.0

Method Mean Error StdDev Gen 0 Gen 1 Gen 2 Allocated
.NET Default Parse(String) 5.533 ms 0.1095 ms 0.1499 ms 93.7500 93.7500 93.7500 390.82 KB
Home brewed Parse(String) 3.406 ms 0.0519 ms 0.0460 ms 39.0625 39.0625 39.0625 390.71 KB
FastEnum Parse(String) 2.224 ms 0.0355 ms 0.0315 ms 39.0625 39.0625 39.0625 390.71 KB
Enums.NET Parse(String) 2.848 ms 0.0545 ms 0.0535 ms 101.5625 101.5625 101.5625 390.73 KB
.NET Default Parse(Span) 5.367 ms 0.0475 ms 0.0421 ms 39.0625 39.0625 39.0625 390.75 KB
Home brewed Parse(Span.ToString()) 3.547 ms 0.0369 ms 0.0345 ms 39.0625 39.0625 39.0625 390.71 KB
FastEnum Parse(Span.ToString()) 2.397 ms 0.0264 ms 0.0247 ms 39.0625 39.0625 39.0625 390.71 KB
Enums.NET Parse(Span.ToString()) 3.416 ms 0.0682 ms 0.0911 ms 101.5625 101.5625 101.5625 390.73 KB

Analysis

The problem almost certainly in not enough caching. All of these different solutions revolve around using reflection at runtime to look up the values of these values. The faster solutions generally use more caching to speed up performance. I'd suggest the .NET implementation either take a more aggressive approach to caching and or potentially look into using source generators to negate the startup cost associated with the caching altogether.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions