Benchmarks

February 3, 2026 · View on GitHub

Environment

BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061)
AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 9.0.300
  [Host]   : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2
  .NET 8.0 : .NET 8.0.16 (8.0.1625.21506), X64 RyuJIT AVX2
  .NET 9.0 : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2

All benchmarks are currenlty using 1k elements. Benchmarks showing the scaling is on the roadmap - expect IndexedSet to scale much much better than the naive LINQ Queries. As .NET 9.0 brings in a bunch of performance improvements, the benchmarks are run against both .NET 8 and 9.

Unique-Index

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDCode SizeGen0AllocatedAlloc Ratio
Unqiue_Linq.NET 10.0.NET 10.013,148.3 ns261.58 ns601.02 ns12,869.1 ns1.000.06721 B0.51888800 B1.00
Unique_Dictionary.NET 10.0.NET 10.0288.7 ns5.37 ns10.96 ns283.7 ns0.020.00524 B--0.00
Unique_IndexedSet_PrimaryKey.NET 10.0.NET 10.0814.4 ns16.15 ns30.33 ns801.2 ns0.060.00769 B--0.00
Unique_IndexedSet_Single.NET 10.0.NET 10.0725.4 ns14.40 ns29.42 ns710.7 ns0.060.00861 B--0.00
Unqiue_Linq.NET 9.0.NET 9.010,493.8 ns208.35 ns370.34 ns10,403.4 ns1.000.051,005 B0.51888800 B1.00
Unique_Dictionary.NET 9.0.NET 9.0414.5 ns8.32 ns15.62 ns405.5 ns0.040.00485 B--0.00
Unique_IndexedSet_PrimaryKey.NET 9.0.NET 9.0833.0 ns24.01 ns70.80 ns778.8 ns0.080.01754 B--0.00
Unique_IndexedSet_Single.NET 9.0.NET 9.0822.3 ns1.49 ns1.32 ns822.6 ns0.080.00843 B--0.00

ℹ️ Note that manually maintaining a dictionary is currently faster but only if the executing class has direct access to the dictionary. Ideas to bring it on par are being explored for scenarios with very tight loops around dictionary lookup.

MultiValue-Index

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDGen0Code SizeAllocatedAlloc Ratio
MultiValue_Linq.NET 10.0.NET 10.038.952 μs0.7758 μs1.7980 μs38.045 μs1.000.060.06103,774 B1680 B1.00
Multivalue_Lookup.NET 10.0.NET 10.08.504 μs0.1633 μs0.2236 μs8.442 μs0.220.010.01532,531 B288 B0.17
Multivalue_IndexedSet.NET 10.0.NET 10.09.829 μs0.1937 μs0.3493 μs9.643 μs0.250.010.01533,161 B360 B0.21
MultiValue_Linq.NET 9.0.NET 9.036.465 μs0.7226 μs1.5241 μs35.834 μs1.000.060.06103,148 B1680 B1.00
Multivalue_Lookup.NET 9.0.NET 9.08.977 μs0.1777 μs0.3787 μs8.808 μs0.250.010.01532,414 B288 B0.17
Multivalue_IndexedSet.NET 9.0.NET 9.09.394 μs0.1872 μs0.4263 μs9.206 μs0.260.020.01532,772 B360 B0.21

ℹ️ Solution is on par with manually using LINQ's lookup (i.e. .ToLookup())

Range-Index

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDGen0Gen1AllocatedAlloc Ratio
LessThan_Linq.NET 10.0.NET 10.015,706.581 ns74.2673 ns65.8360 ns15,701.244 ns1.000.01--160 B1.00
LessThan_IndexedSet.NET 10.0.NET 10.03,179.832 ns4.1719 ns3.9024 ns3,179.079 ns0.200.000.0038-72 B0.45
LessThan_Linq.NET 9.0.NET 9.04,316.148 ns22.1099 ns20.6816 ns4,321.387 ns1.000.010.0076-160 B1.00
LessThan_IndexedSet.NET 9.0.NET 9.03,634.847 ns40.2365 ns37.6372 ns3,622.781 ns0.840.010.0038-72 B0.45
Min_Linq.NET 10.0.NET 10.014,678.955 ns23.4169 ns20.7585 ns14,678.968 ns1.0000.00---NA
Min_IndexedSet.NET 10.0.NET 10.04.847 ns0.0110 ns0.0098 ns4.847 ns0.0000.00---NA
Min_Linq.NET 9.0.NET 9.037,915.652 ns888.5133 ns2,577.7383 ns37,360.938 ns1.0040.10--40 B1.00
Min_IndexedSet.NET 9.0.NET 9.05.593 ns0.1341 ns0.1966 ns5.558 ns0.0000.00---0.00
Paging_Linq.NET 10.0.NET 10.042,206.163 ns841.4204 ns2,201.8508 ns41,359.338 ns1.0030.079.52151.1597160464 B1.000
Paging_IndexedSet.NET 10.0.NET 10.0106.013 ns2.1498 ns4.4397 ns104.024 ns0.0030.000.0138-232 B0.001
Paging_Linq.NET 9.0.NET 9.044,843.363 ns890.3682 ns2,422.3126 ns43,845.044 ns1.0030.079.52151.1597160464 B1.000
Paging_IndexedSet.NET 9.0.NET 9.0149.767 ns2.9575 ns7.3101 ns146.858 ns0.0030.000.0138-232 B0.001
Range_Linq.NET 10.0.NET 10.019,502.712 ns386.9089 ns857.3635 ns19,081.104 ns1.000.06--160 B1.00
Range_IndexedSet.NET 10.0.NET 10.02,773.752 ns55.4947 ns150.9773 ns2,714.090 ns0.140.010.0038-72 B0.45
Range_Linq.NET 9.0.NET 9.05,084.356 ns101.6391 ns233.5328 ns4,994.598 ns1.000.060.0076-160 B1.00
Range_IndexedSet.NET 9.0.NET 9.03,224.274 ns64.1235 ns147.3343 ns3,152.154 ns0.640.040.0038-72 B0.45

ℹ️ There is no built-in range data structure, hence no comparison. Paging covers sorting scenarios, while min-queries cover MinBy and MaxBy-Queries as well.

Prefix-Index

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDGen0Gen1AllocatedAlloc Ratio
FuzzyStartsWith_Linq.NET 10.0.NET 10.062,237.0 ns1,239.18 ns2,896.55 ns61,720.4 ns1.000.062.3804-40088 B1.000
FuzzyStartsWith_IndexedSet.NET 10.0.NET 10.024,341.0 ns484.00 ns1,214.25 ns23,822.7 ns0.390.03--240 B0.006
FuzzyStartsWith_Autypo.NET 10.0.NET 10.071,487.6 ns1,480.23 ns4,364.49 ns69,695.2 ns1.150.090.3662-6656 B0.166
FuzzyStartsWith_Linq.NET 9.0.NET 9.066,353.2 ns1,285.98 ns1,263.01 ns66,357.9 ns1.000.035.7373-96088 B1.000
FuzzyStartsWith_IndexedSet.NET 9.0.NET 9.024,245.6 ns481.63 ns1,225.90 ns24,021.4 ns0.370.02--240 B0.002
FuzzyStartsWith_Autypo.NET 9.0.NET 9.075,983.9 ns1,517.40 ns3,693.57 ns74,524.4 ns1.150.060.3662-6688 B0.070
StartsWith_Linq.NET 10.0.NET 10.02,412.0 ns76.82 ns225.31 ns2,396.5 ns1.010.130.0076-128 B1.00
StartsWith_IndexedSet.NET 10.0.NET 10.0361.8 ns4.59 ns8.28 ns358.3 ns0.150.010.0086-144 B1.12
StartsWith_Autypo.NET 10.0.NET 10.02,135.0 ns23.38 ns21.87 ns2,137.2 ns0.890.080.3967-6664 B52.06
StartsWith_Linq.NET 9.0.NET 9.01,284.3 ns5.16 ns4.82 ns1,285.4 ns1.000.010.0076-128 B1.00
StartsWith_IndexedSet.NET 9.0.NET 9.0401.3 ns0.97 ns0.91 ns400.9 ns0.310.000.0086-144 B1.12
StartsWith_Autypo.NET 9.0.NET 9.02,166.4 ns24.01 ns22.46 ns2,162.1 ns1.690.020.39670.00386696 B52.31

FullText-Index

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDGen0AllocatedAlloc Ratio
Contains_Linq.NET 10.0.NET 10.05,838.9 ns13.81 ns11.53 ns5,839.7 ns1.000.000.0076176 B1.00
Contains_IndexedSet.NET 10.0.NET 10.0320.4 ns0.99 ns0.83 ns320.4 ns0.050.000.0238400 B2.27
Contains_Linq.NET 9.0.NET 9.05,554.7 ns110.00 ns306.63 ns5,413.8 ns1.000.080.0076176 B1.00
Contains_IndexedSet.NET 9.0.NET 9.0371.0 ns7.44 ns12.63 ns367.1 ns0.070.000.0238400 B2.27
FuzzyContains_Linq.NET 10.0.NET 10.07,426,216.0 ns149,406.59 ns438,183.70 ns7,290,326.6 ns1.000.08281.25004831736 B1.000
FuzzyContains_IndexedSet.NET 10.0.NET 10.0215,252.5 ns4,270.85 ns8,914.85 ns212,033.3 ns0.030.00-608 B0.000
FuzzyContains_Linq.NET 9.0.NET 9.07,401,515.6 ns145,233.19 ns254,364.24 ns7,416,882.8 ns1.000.05281.25004831736 B1.000
FuzzyContains_IndexedSet.NET 9.0.NET 9.0221,219.2 ns4,371.49 ns9,027.88 ns216,925.3 ns0.030.00-608 B0.000

ℹ️ Fuzzy-Matching of string pairs within the Linq-Benchmark is done with Fastenshtein and enumerating possible infixes.

ConcurrentSet

MethodJobRuntimeMeanErrorStdDevMedianRatioRatioSDAllocatedAlloc Ratio
FullTextLookup.NET 10.0.NET 10.026,927.069 ns531.1862 ns1,209.7799 ns26,783.429 ns1.000.06424 B1.00
ConcurrentFullTextLookup.NET 10.0.NET 10.027,261.862 ns796.1398 ns2,309.7462 ns26,265.707 ns1.010.10464 B1.09
FullTextLookup.NET 9.0.NET 9.026,550.452 ns525.4506 ns1,186.0296 ns25,940.977 ns1.000.06424 B1.00
ConcurrentFullTextLookup.NET 9.0.NET 9.026,556.150 ns516.2834 ns1,206.7959 ns26,321.509 ns1.000.06464 B1.09
LessThanLookup.NET 10.0.NET 10.09.895 ns0.2172 ns0.4185 ns9.726 ns1.000.06-NA
ConcurrentLessThanLookup.NET 10.0.NET 10.023.018 ns0.4820 ns0.8934 ns22.627 ns2.330.13-NA
LessThanLookup.NET 9.0.NET 9.012.156 ns0.2669 ns0.5206 ns12.066 ns1.000.06-NA
ConcurrentLessThanLookup.NET 9.0.NET 9.060.554 ns1.2057 ns1.1841 ns60.056 ns4.990.23-NA
UniqueLookup.NET 10.0.NET 10.06.976 ns0.1641 ns0.4709 ns6.932 ns1.000.09-NA
ConcurrentUniqueLookup.NET 10.0.NET 10.015.597 ns0.0534 ns0.0499 ns15.581 ns2.250.14-NA
UniqueLookup.NET 9.0.NET 9.09.318 ns0.0222 ns0.0197 ns9.315 ns1.000.00-NA
ConcurrentUniqueLookup.NET 9.0.NET 9.024.100 ns0.2788 ns0.2328 ns24.143 ns2.590.02-NA

ℹ️ For more complex scenarios, the synchronization cost is negligible. For simple queries, it is not. The difference in allocated memory is due to materialization of results in the concurrent case.