Skip to content

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

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

MethodMeanErrorStdDevGen 0Gen 1Gen 2Allocated
Default5,772.3 us115.05 us127.88 us585.9375203.1250203.12503125.82 KB
EnumHelper679.0 us6.74 us6.30 us179.6875179.6875179.6875781.42 KB
FastEnum633.7 us8.35 us7.41 us181.6406181.6406181.6406781.39 KB
EnumsDotnet664.7 us9.78 us9.14 us181.6406181.6406181.6406781.38 KB

ToString net7.0

MethodMeanErrorStdDevGen 0Gen 1Gen 2Allocated
Default4,859.5 us86.12 us199.59 us562.5000187.5000187.50003125.69 KB
EnumHelper736.1 us7.41 us6.93 us179.6875179.6875179.6875781.4 KB
FastEnum747.4 us14.82 us27.48 us181.6406181.6406181.6406781.42 KB
EnumsDotnet779.1 us14.32 us20.54 us181.6406181.6406181.6406781.4 KB

Parse net6.0

MethodMeanErrorStdDevGen 0Gen 1Gen 2Allocated
.NET Default Parse(String)7.005 ms0.1261 ms0.2071 ms78.125078.125078.1250390.81 KB
Home brewed Parse(String)3.833 ms0.0741 ms0.0854 ms101.5625101.5625101.5625390.73 KB
FastEnum Parse(String)2.724 ms0.0542 ms0.0580 ms101.5625101.5625101.5625390.73 KB
Enums.NET Parse(String)3.073 ms0.0356 ms0.0333 ms101.5625101.5625101.5625390.73 KB
.NET Default Parse(Span)6.840 ms0.0797 ms0.0745 ms93.750093.750093.7500390.81 KB
Home brewed Parse(Span.ToString())4.129 ms0.0821 ms0.1124 ms93.750093.750093.7500390.73 KB
FastEnum Parse(Span.ToString())3.036 ms0.0596 ms0.0586 ms101.5625101.5625101.5625390.73 KB
Enums.NET Parse(Span.ToString())3.109 ms0.0482 ms0.0451 ms101.5625101.5625101.5625390.76 KB

Parse net7.0

MethodMeanErrorStdDevGen 0Gen 1Gen 2Allocated
.NET Default Parse(String)5.533 ms0.1095 ms0.1499 ms93.750093.750093.7500390.82 KB
Home brewed Parse(String)3.406 ms0.0519 ms0.0460 ms39.062539.062539.0625390.71 KB
FastEnum Parse(String)2.224 ms0.0355 ms0.0315 ms39.062539.062539.0625390.71 KB
Enums.NET Parse(String)2.848 ms0.0545 ms0.0535 ms101.5625101.5625101.5625390.73 KB
.NET Default Parse(Span)5.367 ms0.0475 ms0.0421 ms39.062539.062539.0625390.75 KB
Home brewed Parse(Span.ToString())3.547 ms0.0369 ms0.0345 ms39.062539.062539.0625390.71 KB
FastEnum Parse(Span.ToString())2.397 ms0.0264 ms0.0247 ms39.062539.062539.0625390.71 KB
Enums.NET Parse(Span.ToString())3.416 ms0.0682 ms0.0911 ms101.5625101.5625101.5625390.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