Regex Performance With and Without RegexOptions.Compiled Using .NET Framework 4.8 and .NET Core 3.1 (December 2019)

Written by Ken Dale

We’ve had some internal discussion around the usage of RegexOptions.Compiled in .NET — how it works and when it’s appropriate to use it. Despite being named Compiled is isn’t compiled at build time, it’s a runtime optimization for repeated use of a regex. And, it’s backed by a cache that has a default limit of 15. You can read more about this here: https://docs.microsoft.com/en-us/dotnet/standard/base-types/compilation-and-reuse-in-regular-expressions.

That said, what are the performance characteristics of normal and compiled regular expressions on .NET Framework 4.8 and .NET Core 3.1?

Benchmark results

https://github.com/kendaleiv/dotnet-regex-benchmarks#results

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i7-8550U CPU 1.80GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.100
  [Host]        : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
  .NET Core x64 : .NET Core 3.1.0 (CoreCLR 4.700.19.56402, CoreFX 4.700.19.56404), X64 RyuJIT
  LegacyJitX86  : .NET Framework 4.8 (4.8.4042.0), X86 LegacyJIT
  RyuJitX64     : .NET Framework 4.8 (4.8.4042.0), X64 RyuJIT

Single

Single barplot{: .img-fluid .border }

Method JobJitPlatformToolchain MeanErrorStdDevGen 0Gen 1Gen 2Allocated
Normal.NET Core x64RyuJitX64.NET Core x641.799 us0.0360 us0.0658 us0.7629--3.12 KB
Compiled.NET Core x64RyuJitX64.NET Core x641,134.949 us6.3422 us5.6222 us1.9531--12.69 KB
NormalLegacyJitX86LegacyJitX86net482.380 us0.0472 us0.0505 us0.7172--2.94 KB
CompiledLegacyJitX86LegacyJitX86net48631.919 us9.4512 us8.8406 us1.95310.9766-9.41 KB
NormalRyuJitX64RyuJitX64net482.076 us0.0144 us0.0112 us1.1139--4.57 KB
CompiledRyuJitX64RyuJitX64net481,167.988 us22.9845 us34.4021 us1.9531--14.37 KB

1,000

1,000 barplot{: .img-fluid .border }

Method JobJitPlatformToolchainMeanErrorStdDevMedianGen 0Gen 1Gen 2Allocated
Normal.NET Core x64RyuJitX64.NET Core x64174.0 us3.47 us7.61 us171.0 us50.2930--206.04 KB
Compiled.NET Core x64RyuJitX64.NET Core x642,039.4 us40.86 us114.59 us2,038.8 us52.73441.9531-215.63 KB
NormalLegacyJitX86LegacyJitX86net48282.0 us10.30 us30.06 us269.0 us30.2734--124.09 KB
CompiledLegacyJitX86LegacyJitX86net481,000.6 us20.06 us28.12 us999.3 us31.2500--130.78 KB
NormalRyuJitX64RyuJitX64net48250.2 us9.52 us27.92 us256.2 us50.7813--208.09 KB
CompiledRyuJitX64RyuJitX64net481,361.3 us25.39 us26.07 us1,369.7 us52.73441.9531-218.27 KB

100,000

100,000 barplot{: .img-fluid .border }

Method JobJitPlatformToolchainMeanErrorStdDevGen 0Gen 1Gen 2Allocated
Normal.NET Core x64RyuJitX64.NET Core x6417.08 ms0.341 ms0.466 ms4968.7500--19.84 MB
Compiled.NET Core x64RyuJitX64.NET Core x6416.94 ms0.245 ms0.229 ms4968.7500--19.85 MB
NormalLegacyJitX86LegacyJitX86net4820.29 ms0.402 ms0.683 ms2937.5000--11.85 MB
CompiledLegacyJitX86LegacyJitX86net4817.47 ms0.345 ms0.751 ms2937.500031.2500-11.85 MB
NormalRyuJitX64RyuJitX64net4819.45 ms0.379 ms0.673 ms4968.7500--19.9 MB
CompiledRyuJitX64RyuJitX64net4816.07 ms0.319 ms0.777 ms4968.7500--19.91 MB

1,000,000

1,000,000 barplot{: .img-fluid .border }

Method JobJitPlatformToolchainMeanErrorStdDevMedianGen 0Gen 1Gen 2Allocated
Normal.NET Core x64RyuJitX64.NET Core x64170.3 ms6.86 ms8.17 ms166.6 ms49500.0000--198.37 MB
Compiled.NET Core x64RyuJitX64.NET Core x64145.0 ms2.86 ms6.50 ms144.0 ms49500.0000--198.38 MB
NormalLegacyJitX86LegacyJitX86net48203.5 ms4.02 ms6.60 ms202.3 ms29333.3333--118.43 MB
CompiledLegacyJitX86LegacyJitX86net48173.8 ms3.36 ms4.93 ms172.5 ms29500.0000--118.44 MB
NormalRyuJitX64RyuJitX64net48207.7 ms4.46 ms13.07 ms203.7 ms49666.6667--198.95 MB
CompiledRyuJitX64RyuJitX64net48141.9 ms1.92 ms1.80 ms142.2 ms49500.0000--198.96 MB

Conclusion

Using a compiled regex for a single result is a performance burden. Using a compiled regex as a performance optimization doesn’t make sense until you’re matching it many times (at least on the hardware described by this benchmark). With performance optimizations ideally one measures to ensure performance is improved and by how much. And, don’t forget to consider the cache size if you’re using compiled regex.

The benchmark is at https://github.com/kendaleiv/dotnet-regex-benchmarks if you’d like to try it out yourself.

Published December 10, 2019 by

undefined avatar
Ken Dale Senior Application Developer (Former)

Suggested Reading