Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Avoid generic virtual dispatch for frozen collections alternate lookup - #108732

Merged
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch
Jan 28, 2025
Merged

Avoid generic virtual dispatch for frozen collections alternate lookup#108732
stephentoub merged 9 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-alternate-lookup-generic-virtual-dispatch

Conversation

@andrewjsaid

Copy link
Copy Markdown
Contributor

Background

In .NET 9 the Alternate Lookup concept was added to dictionaries where you could look up in a dictionary by a key type different than the original one. The main motivator was reading data off the wire e.g. keying a Dictionary<string, T> using a ReadOnlySpan<char> (or byte). Thus I created a PR in ASP.NET Core to use this new method on FrozenDictionary<string, T> when matching routing but was surprised to find that there's quite a hefty penalty when using Alternate Lookups due to the generic virtual dispatch mechanism itself being a dictionary. Thus while the memory allocations disappeared the performance in terms of speed improved only slightly in the microbenchmarks.

See dotnet/aspnetcore#58305

Solution

FrozenDictionary<string, T> currently exposes a method with the following signature where the actual work happens.

privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCore<TAlternateKey>(TAlternateKeykey)

To replace it, a new mechanism was added:

privateprotectedvirtualAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()internaldelegateref readonly TValueAlternateLookupDelegate<TAlternateKey>(FrozenDictionary<TKey,TValue>dictionary,TAlternateKeykey);

The idea is that when the AlternateLookup struct is created it pays the price of the generic virtual dispatch once. It can then use the delegate as follows to avoid generic virtual dispatch:

ref readonly TValuevalueRef=ref_alternateLookupDelegate(Dictionary,key);

In concrete types of FrozenDictionary we can then use the following pattern to cache a static instance of the delegate which calls the actual worker method.

// pseudocode - some details removedprivateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>()=>AlternateKeyDelegateHolder<TAlternateKey>.Instance;privatestaticclassAlternateKeyDelegateHolder<TAlternateKey>{publicstaticAlternateLookupDelegate<TAlternateKey>Instance=(dictionary,key)=>ref((DefaultFrozenDictionary<TKey,TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);}privateref readonly TValueGetValueRefOrNullRefCoreAlternate<TAlternateKey>(TAlternateKeykey){
...}

In the above example GetAlternateLookupDelegate<TAlternateKey> simply loads a singleton delegate from a nested generic type. The delegate is implemented by calling GetValueRefOrNullRefCoreAlternate<TAlternateKey which although generic importantly is not a virtual method.

In order to avoid repeating this pattern for all the classes inheriting from OrdinalStringFrozenDictionary, a different pattern is used there. Note that in this case we do not need generics as we know that the concrete type of TAlternateKey will be ReadOnlySpan<char>, a fact which is asserted when creating the lookup.

privatestaticAlternateLookupDelegate<ReadOnlySpan<char>>s_alternateLookup=(dictionary,key)=>ref((OrdinalStringFrozenDictionary<TValue>)dictionary).GetValueRefOrNullRefCoreAlternate(key);privateprotectedoverrideAlternateLookupDelegate<TAlternateKey>GetAlternateLookupDelegate<TAlternateKey>(){Debug.Assert(typeof(TAlternateKey)==typeof(ReadOnlySpan<char>));return(AlternateLookupDelegate<TAlternateKey>)(object)s_alternateLookup;}[MethodImpl(MethodImplOptions.AggressiveInlining)]privateprotectedvirtualref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key){
...existing code
}

In the above example we are able to remove the generics from the worker method, and thus we can make it virtual - still avoiding the combination of generics and virtual dispatch. Thus the derived types can use the existing approach as follows.

privateprotectedoverrideref readonly TValueGetValueRefOrNullRefCoreAlternate(ReadOnlySpan<char>key)=>refbase.GetValueRefOrNullRefCoreAlternate(key);

Note that further investigation is necessary to measure whether it is a good idea to remove the virtual dispatch entirely even for classes inheriting OrdinalStringFrozenDictionary. It would result in more complex code but could be faster depending on how expensive the (non-generic) virtual dispatch is.

Conclusion

As we can see from the benchmarks below, the PR does not change the "normal" TryGetValue scenario and improves the "Alternate Lookup" TryGetValue scenario significantly.

This way the alternate lookup still suffers a performance penalty but it's not quite as significant and is more likely to justify it's use if it results in fewer allocations.

Benchmark Results

dotnet/performance Frozen Dictionary benchmarks (https://github.com/dotnet/performance/blob/main/src/benchmarks/micro/libraries/System.Collections/Frozen/Perf_FrozenDictionary_String.cs) were modified locally to add a case to use the AlternateLookup feature. I didn't create a PR as I couldn't find a clean way to add it with conditional compilation for .NET 9+

DefaultFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1024.82 ns0.233 ns0.218 ns24.74 ns24.45 ns25.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.93 ns0.249 ns0.233 ns24.91 ns24.65 ns25.35 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain1023.37 ns0.207 ns0.184 ns23.30 ns23.16 ns23.81 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1022.61 ns0.174 ns0.154 ns22.55 ns22.47 ns22.93 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10141.36 ns0.659 ns0.584 ns141.34 ns140.08 ns142.49 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.76 ns0.461 ns0.409 ns62.84 ns61.81 ns63.13 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10121.20 ns1.008 ns0.943 ns121.08 ns119.80 ns122.92 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1046.76 ns0.294 ns0.261 ns46.65 ns46.43 ns47.30 ns0.390.00-NA
TryGetValue_True_FrozenDictionarymain100835.82 ns9.389 ns8.783 ns839.17 ns821.16 ns851.62 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100812.22 ns8.532 ns7.563 ns815.04 ns800.04 ns822.71 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain100566.00 ns1.487 ns1.161 ns565.81 ns564.35 ns569.07 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr100565.69 ns3.029 ns2.833 ns564.75 ns562.08 ns571.06 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1002,006.62 ns13.648 ns12.767 ns2,000.68 ns1,990.83 ns2,024.54 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001,196.25 ns7.592 ns7.102 ns1,193.14 ns1,190.27 ns1,211.04 ns0.600.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,467.58 ns13.478 ns12.607 ns1,467.24 ns1,447.26 ns1,491.49 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100809.94 ns4.464 ns4.176 ns811.04 ns802.96 ns815.78 ns0.550.01-NA
TryGetValue_True_FrozenDictionarymain10008,698.64 ns42.144 ns39.422 ns8,711.26 ns8,636.79 ns8,771.65 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10008,634.08 ns29.948 ns26.548 ns8,631.32 ns8,574.59 ns8,669.79 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10007,982.33 ns67.440 ns63.083 ns8,020.97 ns7,910.93 ns8,060.76 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10008,026.40 ns11.676 ns9.750 ns8,024.86 ns8,012.28 ns8,051.13 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100019,852.27 ns150.477 ns140.757 ns19,923.25 ns19,617.70 ns20,001.71 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100012,404.54 ns31.612 ns29.570 ns12,407.62 ns12,340.32 ns12,449.85 ns0.620.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100016,470.71 ns77.600 ns72.587 ns16,491.01 ns16,215.28 ns16,518.22 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100010,038.52 ns56.170 ns52.541 ns10,056.95 ns9,911.11 ns10,084.57 ns0.610.00-NA
TryGetValue_True_FrozenDictionarymain10000173,394.89 ns765.735 ns639.423 ns173,490.45 ns171,768.40 ns174,356.74 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000168,422.47 ns2,242.735 ns2,097.856 ns167,781.79 ns165,855.58 ns172,569.42 ns0.970.01-NA
TryGetValue_False_FrozenDictionarymain10000138,800.01 ns995.304 ns931.008 ns138,332.41 ns137,750.06 ns140,323.01 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10000142,408.54 ns1,163.682 ns1,031.574 ns142,248.41 ns140,787.56 ns144,191.36 ns1.030.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000330,175.06 ns5,147.664 ns4,815.128 ns332,063.56 ns324,248.27 ns338,772.74 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000223,406.27 ns2,984.179 ns2,791.403 ns224,066.34 ns218,652.50 ns227,634.64 ns0.680.011 B1.00
TryGetValue_False_FrozenDictionaryLookupmain10000233,352.88 ns950.977 ns742.460 ns233,392.26 ns232,108.02 ns234,459.24 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000172,137.36 ns1,150.259 ns960.519 ns172,313.38 ns170,510.33 ns173,503.94 ns0.740.00-0.00
LengthBucketsFrozenDictionary
MethodToolchainCountItemsPerBucketMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain10119.902 ns0.1293 ns0.1080 ns19.914 ns19.732 ns20.044 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10120.099 ns0.1980 ns0.1852 ns20.111 ns19.815 ns20.473 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1018.641 ns0.1863 ns0.1742 ns8.630 ns8.381 ns9.011 ns1.000.03-NA
TryGetValue_False_FrozenDictionarypr1018.827 ns0.1284 ns0.1138 ns8.823 ns8.648 ns9.015 ns1.020.02-NA
TryGetValue_True_FrozenDictionaryLookupmain101142.569 ns0.8472 ns0.7924 ns142.567 ns141.324 ns144.147 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10150.636 ns0.3343 ns0.3127 ns50.754 ns50.135 ns51.069 ns0.360.00-NA
TryGetValue_False_FrozenDictionaryLookupmain101105.402 ns0.7166 ns0.6352 ns105.667 ns104.272 ns106.178 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10118.745 ns0.1785 ns0.1670 ns18.683 ns18.514 ns19.066 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain10575.388 ns0.4839 ns0.4527 ns75.218 ns74.953 ns76.199 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10575.815 ns0.6499 ns0.6079 ns76.034 ns74.771 ns76.590 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain1058.964 ns0.0512 ns0.0478 ns8.966 ns8.873 ns9.049 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1058.990 ns0.0672 ns0.0596 ns8.986 ns8.867 ns9.106 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain105198.743 ns1.6434 ns1.3723 ns199.309 ns196.262 ns199.941 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr105114.005 ns0.7425 ns0.6582 ns113.873 ns113.278 ns115.215 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain105105.027 ns0.7483 ns0.7000 ns105.267 ns103.690 ns105.595 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10518.512 ns0.2034 ns0.1803 ns18.572 ns18.258 ns18.764 ns0.180.00-NA
TryGetValue_True_FrozenDictionarymain1001190.921 ns1.2957 ns1.2120 ns190.475 ns189.531 ns192.608 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1001191.816 ns1.2793 ns1.1341 ns192.120 ns189.424 ns193.094 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100172.753 ns0.5778 ns0.5405 ns72.924 ns71.989 ns73.566 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100173.241 ns0.5564 ns0.4932 ns73.311 ns72.368 ns73.965 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10011,421.168 ns13.9540 ns11.6522 ns1,419.412 ns1,402.696 ns1,449.842 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1001494.229 ns5.8461 ns5.4685 ns496.437 ns481.493 ns499.093 ns0.350.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10011,041.986 ns6.3858 ns5.6609 ns1,042.127 ns1,034.252 ns1,055.021 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1001179.455 ns1.1414 ns0.9531 ns179.811 ns177.226 ns180.135 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain1005773.623 ns5.2743 ns4.9336 ns775.966 ns763.302 ns778.488 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1005772.435 ns4.6678 ns4.1379 ns773.435 ns760.933 ns775.983 ns1.000.01-NA
TryGetValue_False_FrozenDictionarymain100573.001 ns0.3449 ns0.3226 ns73.078 ns72.371 ns73.569 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100572.374 ns0.3982 ns0.3325 ns72.306 ns71.948 ns73.207 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10051,987.912 ns17.1403 ns16.0331 ns1,995.164 ns1,956.139 ns2,005.592 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10051,137.256 ns11.9771 ns11.2034 ns1,134.633 ns1,123.508 ns1,156.275 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10051,040.490 ns8.2041 ns7.6741 ns1,042.214 ns1,028.159 ns1,052.716 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1005178.391 ns1.5918 ns1.4890 ns177.725 ns176.828 ns181.733 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100012,142.619 ns9.0847 ns7.0928 ns2,141.511 ns2,134.290 ns2,159.042 ns1.000.00-NA
TryGetValue_True_FrozenDictionarypr100012,124.494 ns17.4370 ns15.4574 ns2,118.313 ns2,108.085 ns2,157.025 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10001964.540 ns5.0478 ns4.7217 ns962.929 ns959.170 ns973.696 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr10001962.343 ns8.6689 ns7.2389 ns960.085 ns955.258 ns979.858 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000114,031.125 ns45.7480 ns40.5544 ns14,022.326 ns13,967.770 ns14,109.108 ns1.000.00-NA
TryGetValue_True_FrozenDictionaryLookuppr100014,775.847 ns14.4875 ns12.0977 ns4,782.256 ns4,756.205 ns4,790.342 ns0.340.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1000110,419.083 ns118.5014 ns105.0484 ns10,369.367 ns10,325.502 ns10,686.005 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100011,751.941 ns4.3219 ns3.6090 ns1,752.104 ns1,747.471 ns1,760.421 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain100057,563.892 ns55.5537 ns49.2469 ns7,545.850 ns7,512.330 ns7,672.709 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100057,818.676 ns19.7715 ns15.4363 ns7,819.761 ns7,793.648 ns7,842.421 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain10005915.250 ns1.7844 ns1.5818 ns915.346 ns912.014 ns917.874 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10005910.566 ns12.8763 ns12.0445 ns914.497 ns891.573 ns929.127 ns0.990.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1000519,447.402 ns170.0298 ns150.7270 ns19,370.762 ns19,286.242 ns19,729.391 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000511,133.069 ns92.3892 ns81.9006 ns11,139.811 ns10,872.462 ns11,221.569 ns0.570.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1000510,437.497 ns23.2264 ns21.7260 ns10,431.583 ns10,410.619 ns10,475.481 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr100051,763.392 ns10.4994 ns8.7674 ns1,766.299 ns1,752.242 ns1,776.463 ns0.170.00-NA
TryGetValue_True_FrozenDictionarymain10000144,812.807 ns236.4552 ns221.1803 ns44,755.250 ns44,492.446 ns45,244.679 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10000146,652.041 ns316.4328 ns264.2357 ns46,742.992 ns45,857.045 ns46,815.311 ns1.040.01-NA
TryGetValue_False_FrozenDictionarymain10000124,743.250 ns82.2079 ns72.8752 ns24,733.853 ns24,628.742 ns24,873.955 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000124,348.403 ns128.3899 ns120.0960 ns24,298.454 ns24,211.070 ns24,574.111 ns0.980.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100001175,395.295 ns2,061.6356 ns1,928.4552 ns175,724.157 ns172,247.612 ns179,175.772 ns1.000.021 B1.00
TryGetValue_True_FrozenDictionaryLookuppr10000173,207.400 ns916.1985 ns857.0126 ns73,653.932 ns71,877.670 ns74,152.553 ns0.420.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100001187,889.132 ns527.7729 ns467.8569 ns187,847.210 ns187,278.943 ns188,844.122 ns1.000.001 B1.00
TryGetValue_False_FrozenDictionaryLookuppr10000138,704.869 ns477.7125 ns446.8526 ns38,588.712 ns38,127.456 ns39,391.137 ns0.210.00-0.00
TryGetValue_True_FrozenDictionarymain100005123,066.227 ns746.0009 ns697.8097 ns122,825.843 ns122,026.190 ns124,332.341 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100005123,737.585 ns1,127.2591 ns1,054.4389 ns124,112.354 ns122,209.229 ns125,384.033 ns1.010.011 BNA
TryGetValue_False_FrozenDictionarymain10000521,487.233 ns54.2720 ns45.3195 ns21,497.945 ns21,402.457 ns21,573.108 ns1.000.00-NA
TryGetValue_False_FrozenDictionarypr10000521,377.274 ns56.0457 ns43.7568 ns21,374.646 ns21,284.362 ns21,442.488 ns0.990.00-NA
TryGetValue_True_FrozenDictionaryLookupmain100005237,950.423 ns2,776.5169 ns2,597.1556 ns236,306.250 ns234,870.865 ns241,643.365 ns1.000.011 B1.00
TryGetValue_True_FrozenDictionaryLookuppr100005149,147.012 ns340.2931 ns318.3104 ns149,128.988 ns148,492.798 ns149,720.476 ns0.630.01-0.00
TryGetValue_False_FrozenDictionaryLookupmain100005135,478.543 ns1,252.3472 ns1,171.4464 ns135,349.306 ns134,010.897 ns138,020.673 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10000534,230.881 ns322.2512 ns301.4340 ns34,058.756 ns33,992.887 ns34,774.708 ns0.250.00-NA
SingleCharFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1025.03 ns0.243 ns0.228 ns25.10 ns24.57 ns25.29 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1024.90 ns0.222 ns0.186 ns24.86 ns24.67 ns25.20 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1019.65 ns0.162 ns0.143 ns19.63 ns19.41 ns19.94 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1019.77 ns0.305 ns0.285 ns19.70 ns19.46 ns20.37 ns1.010.02-NA
TryGetValue_True_FrozenDictionaryLookupmain10140.36 ns1.577 ns1.475 ns140.66 ns137.81 ns143.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1062.38 ns0.469 ns0.416 ns62.52 ns61.58 ns62.96 ns0.440.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10109.58 ns0.625 ns0.488 ns109.66 ns108.76 ns110.50 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1044.69 ns0.432 ns0.404 ns44.44 ns44.09 ns45.20 ns0.410.00-NA
TryGetValue_True_FrozenDictionarymain100233.23 ns1.298 ns1.084 ns232.70 ns232.12 ns235.95 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100235.70 ns3.162 ns2.803 ns234.32 ns232.76 ns241.40 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100185.09 ns1.358 ns1.204 ns184.73 ns183.93 ns188.03 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100185.88 ns1.629 ns1.444 ns185.39 ns184.27 ns189.42 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,393.50 ns12.781 ns11.956 ns1,386.02 ns1,382.84 ns1,414.38 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100629.05 ns3.471 ns3.246 ns629.29 ns623.57 ns633.37 ns0.450.00-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,088.17 ns11.327 ns10.041 ns1,087.82 ns1,076.58 ns1,107.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100440.47 ns2.459 ns2.300 ns440.07 ns436.87 ns445.37 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain10002,336.32 ns22.456 ns21.006 ns2,326.63 ns2,312.30 ns2,374.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10002,353.42 ns22.241 ns20.805 ns2,354.64 ns2,328.50 ns2,391.99 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain10001,840.32 ns22.863 ns21.386 ns1,831.90 ns1,816.67 ns1,886.92 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10001,846.40 ns11.044 ns9.222 ns1,847.02 ns1,819.15 ns1,858.81 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100013,916.96 ns120.841 ns113.035 ns13,860.22 ns13,811.26 ns14,182.77 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10006,188.74 ns31.218 ns29.202 ns6,191.14 ns6,092.64 ns6,212.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain100010,887.83 ns107.889 ns95.641 ns10,908.08 ns10,760.05 ns11,016.79 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr10004,404.70 ns36.506 ns30.484 ns4,415.44 ns4,328.23 ns4,426.62 ns0.400.00-NA
TryGetValue_True_FrozenDictionarymain1000023,729.13 ns237.473 ns222.132 ns23,613.20 ns23,526.62 ns24,179.01 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000023,474.17 ns213.858 ns200.043 ns23,347.04 ns23,302.09 ns23,809.62 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1000018,378.85 ns178.328 ns158.083 ns18,322.70 ns18,160.98 ns18,636.55 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000018,669.90 ns102.371 ns85.484 ns18,700.51 ns18,483.41 ns18,790.11 ns1.020.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000139,030.12 ns1,236.323 ns1,095.968 ns138,585.77 ns137,877.46 ns141,096.93 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1000061,809.42 ns561.201 ns497.490 ns61,818.97 ns61,136.20 ns62,674.75 ns0.440.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000108,375.03 ns776.887 ns726.701 ns107,949.27 ns107,731.72 ns109,919.27 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr1000044,318.31 ns405.006 ns338.198 ns44,431.91 ns43,727.75 ns44,770.61 ns0.410.00-NA
SubstringFrozenDictionary
MethodToolchainCountMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
TryGetValue_True_FrozenDictionarymain1045.27 ns0.500 ns0.468 ns45.44 ns44.60 ns45.85 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1045.02 ns0.415 ns0.388 ns45.16 ns44.07 ns45.47 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain1040.94 ns0.396 ns0.351 ns41.08 ns40.23 ns41.23 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1040.96 ns0.407 ns0.339 ns40.88 ns40.26 ns41.73 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10162.46 ns1.747 ns1.634 ns162.78 ns159.01 ns164.37 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr1086.32 ns0.231 ns0.193 ns86.25 ns86.12 ns86.83 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain10134.82 ns0.525 ns0.465 ns134.65 ns134.17 ns135.47 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr1066.03 ns0.621 ns0.581 ns66.04 ns65.37 ns67.28 ns0.490.00-NA
TryGetValue_True_FrozenDictionarymain100443.41 ns2.826 ns2.360 ns443.62 ns438.81 ns446.23 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr100449.38 ns3.174 ns2.814 ns450.13 ns443.66 ns453.04 ns1.010.01-NA
TryGetValue_False_FrozenDictionarymain100401.50 ns3.153 ns2.949 ns400.04 ns398.29 ns406.15 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr100405.78 ns2.313 ns2.050 ns405.61 ns400.66 ns408.83 ns1.010.01-NA
TryGetValue_True_FrozenDictionaryLookupmain1001,722.86 ns11.579 ns9.669 ns1,721.56 ns1,711.42 ns1,748.19 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr100902.06 ns8.528 ns7.560 ns905.34 ns885.24 ns910.35 ns0.520.01-NA
TryGetValue_False_FrozenDictionaryLookupmain1001,510.85 ns14.059 ns13.151 ns1,504.99 ns1,496.83 ns1,543.33 ns1.000.01-NA
TryGetValue_False_FrozenDictionaryLookuppr100675.14 ns11.842 ns11.077 ns675.65 ns662.45 ns699.63 ns0.450.01-NA
TryGetValue_True_FrozenDictionarymain10004,668.98 ns49.785 ns41.573 ns4,677.99 ns4,577.36 ns4,724.13 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr10004,617.27 ns18.507 ns16.406 ns4,614.33 ns4,592.06 ns4,651.68 ns0.990.01-NA
TryGetValue_False_FrozenDictionarymain10004,211.73 ns49.914 ns46.690 ns4,202.37 ns4,153.66 ns4,298.69 ns1.000.02-NA
TryGetValue_False_FrozenDictionarypr10004,223.29 ns31.447 ns24.552 ns4,228.95 ns4,164.89 ns4,250.04 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookupmain100017,170.39 ns182.861 ns171.049 ns17,211.10 ns16,890.11 ns17,438.16 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10009,088.65 ns86.163 ns80.597 ns9,080.74 ns8,955.43 ns9,243.28 ns0.530.01-NA
TryGetValue_False_FrozenDictionaryLookupmain100013,326.46 ns46.269 ns38.636 ns13,327.35 ns13,265.45 ns13,407.01 ns1.000.00-NA
TryGetValue_False_FrozenDictionaryLookuppr10006,814.56 ns22.840 ns20.247 ns6,818.63 ns6,784.58 ns6,860.84 ns0.510.00-NA
TryGetValue_True_FrozenDictionarymain1000065,610.00 ns432.958 ns383.806 ns65,489.76 ns65,173.58 ns66,425.34 ns1.000.01-NA
TryGetValue_True_FrozenDictionarypr1000067,624.61 ns887.689 ns830.344 ns67,540.84 ns66,634.56 ns68,936.38 ns1.030.01-NA
TryGetValue_False_FrozenDictionarymain1000052,710.36 ns497.305 ns465.179 ns52,784.55 ns51,919.11 ns53,485.47 ns1.000.01-NA
TryGetValue_False_FrozenDictionarypr1000051,204.82 ns454.698 ns403.078 ns51,180.96 ns50,629.71 ns52,061.91 ns0.970.01-NA
TryGetValue_True_FrozenDictionaryLookupmain10000226,872.14 ns1,032.339 ns965.651 ns226,859.96 ns224,795.47 ns228,671.11 ns1.000.01-NA
TryGetValue_True_FrozenDictionaryLookuppr10000116,339.56 ns301.862 ns252.068 ns116,278.31 ns116,054.50 ns116,958.27 ns0.510.00-NA
TryGetValue_False_FrozenDictionaryLookupmain10000143,789.02 ns1,235.081 ns1,155.296 ns143,830.91 ns142,057.74 ns146,136.64 ns1.000.011 B1.00
TryGetValue_False_FrozenDictionaryLookuppr1000076,627.32 ns354.240 ns295.806 ns76,555.06 ns76,329.85 ns77,399.18 ns0.530.00-0.00

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-collections
See info in area-owners.md if you want to be subscribed.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I would greatly appreciate any feedback on this PR if you have the time

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik I would greatly appreciate any feedback on this PR if you have the time

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid Apologies for the delay in getting this reviewed. We will get back to this as soon as possible.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks reasonable. Thanks for the improvement.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@stephentoub I've completed all the changes and am awaiting next steps.

@stephentoubstephentoub left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks.

@adamsitnik or @eiriktsarpalis , can you please take a look as well? Thanks.

@stephentoub

Copy link
Copy Markdown
Member

/ba-g unrelated interop failure

@stephentoub
stephentoub merged commit ac61528 into dotnet:mainJan 28, 2025
grendello added a commit to grendello/runtime that referenced this pull request Jan 28, 2025
* main: (31 commits)
Fix linux-x86 build (dotnet#111861)
Add FrozenDictionary specialization for integers / enums (dotnet#111886)
[SRM] Refactor reading from streams. (dotnet#111323)
Sign the DAC and DBI during the build process instead of in separate steps (dotnet#111416)
Removing Entry2MethodDesc as it is unnecessary (dotnet#111756)
Cross Product for Vector2 and Vector4 (dotnet#111265)
Handle unicode in absolute URI path for combine. (dotnet#111710)
Drop RequiresProcessIsolation on mcc tests (dotnet#111887)
[main] Update dependencies from dotnet/roslyn (dotnet#111691)
new trimmer feature System.TimeZoneInfo.Invariant (dotnet#111215)
[browser] reduce msbuild memory footprint (dotnet#111751)
Add debugging checks for stack overflow tests failure (dotnet#111867)
Localized file check-in by OneLocBuild Task: Build definition ID 679: Build ID 2629821 (dotnet#111884)
Bump main to preview2 (dotnet#111882)
Avoid generic virtual dispatch for frozen collections alternate lookup (dotnet#108732)
Bump main versioning to preview1 (dotnet#111880)
Switch OneLoc to main (dotnet#111872)
Improve docs on building ILVerify (dotnet#111851)
Update Debian version to 13 (dotnet#111768)
win32: add fallback to environment vars for system folder (dotnet#109673)
...
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 27, 2025
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@andrewjsaid@eiriktsarpalis@stephentoub