Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the new Lock type to Concurrent Dicionary #104575

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,10 @@ internal ConcurrentDictionary(int concurrencyLevel, int capacity, bool growLockA
}
capacity = HashHelpers.GetPrime(capacity);

var locks = new object[concurrencyLevel];
locks[0] = locks; // reuse array as the first lock object just to avoid an additional allocation
for (int i = 1; i < locks.Length; i++)
var locks = new Lock[concurrencyLevel];
for (int i = 0; i < locks.Length; i++)
{
locks[i] = new object();
locks[i] = new Lock();
Copy link
Member

Choose a reason for hiding this comment

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

This is now allocating an extra object. Is it worth it?

Copy link
Member Author

@eduardo-vp eduardo-vp Jul 16, 2024

Choose a reason for hiding this comment

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

The microbenchmarks from the performance repo that use ConcurrentDictionary were tested with this change and got the following results:

// * Summary *

BenchmarkDotNet v0.13.13-nightly.20240311.145, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3) (Hyper-V)
AMD EPYC 7763, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.100-preview.2.24157.14
  [Host]     : .NET 9.0.0 (9.0.24.12805), X64 RyuJIT AVX2
  Job-DHIZMB : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
  Job-XIGAPM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2

PowerPlanMode=00000000-0000-0000-0000-000000000000  IterationTime=250ms  MaxIterationCount=20
MinIterationCount=15  WarmupCount=1

| Type                             | Method               | Job        | Toolchain                | Count | Size | Mean        | Error     | StdDev    | Median      | Min         | Max         | Ratio | RatioSD | Gen0   | Gen1   | Allocated | Alloc Ratio |
|--------------------------------- |--------------------- |----------- |------------------------- |------ |----- |------------:|----------:|----------:|------------:|------------:|------------:|------:|--------:|-------:|-------:|----------:|------------:|
| CtorDefaultSize<Int32>           | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | ?    |    146.9 ns |   1.90 ns |   1.69 ns |    146.9 ns |    144.5 ns |    150.6 ns |  1.00 |    0.02 | 0.0599 |      - |    1008 B |        1.00 |
| CtorDefaultSize<Int32>           | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | ?    |    154.4 ns |   2.94 ns |   2.60 ns |    154.7 ns |    150.6 ns |    158.6 ns |  1.05 |    0.02 | 0.0766 |      - |    1288 B |        1.28 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| CtorDefaultSize<String>          | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | ?    |    153.5 ns |   2.50 ns |   2.34 ns |    152.9 ns |    149.0 ns |    157.1 ns |  1.00 |    0.02 | 0.0601 |      - |    1008 B |        1.00 |
| CtorDefaultSize<String>          | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | ?    |    167.7 ns |   2.21 ns |   1.96 ns |    167.7 ns |    164.0 ns |    171.7 ns |  1.09 |    0.02 | 0.0768 |      - |    1288 B |        1.28 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryAddDefaultSize<Int32>         | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | 512   | ?    | 35,559.7 ns | 142.64 ns | 111.36 ns | 35,587.8 ns | 35,336.2 ns | 35,691.1 ns |  1.00 |    0.00 | 5.7078 | 0.9989 |   96672 B |        1.00 |
| TryAddDefaultSize<Int32>         | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | 512   | ?    | 33,709.7 ns | 193.49 ns | 161.57 ns | 33,719.4 ns | 33,359.2 ns | 33,906.9 ns |  0.95 |    0.01 | 6.2633 | 1.4659 |  104888 B |        1.08 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryAddDefaultSize<String>        | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | 512   | ?    | 47,032.4 ns | 426.37 ns | 356.03 ns | 46,839.7 ns | 46,595.5 ns | 47,572.0 ns |  1.00 |    0.01 | 6.1937 | 1.5015 |  105736 B |        1.00 |
| TryAddDefaultSize<String>        | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | 512   | ?    | 45,092.8 ns | 361.32 ns | 301.71 ns | 45,087.3 ns | 44,725.2 ns | 45,851.2 ns |  0.96 |    0.01 | 6.6643 | 1.6210 |  113952 B |        1.08 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryAddGiventSize<Int32>          | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | 512   | ?    | 24,125.2 ns | 160.18 ns | 141.99 ns | 24,149.0 ns | 23,912.9 ns | 24,347.5 ns |  1.00 |    0.01 | 3.1342 | 0.4609 |   53192 B |        1.00 |
| TryAddGiventSize<Int32>          | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | 512   | ?    | 21,889.2 ns |  87.38 ns |  77.46 ns | 21,894.8 ns | 21,766.5 ns | 22,016.3 ns |  0.91 |    0.01 | 3.1381 | 0.4358 |   53280 B |        1.00 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryAddGiventSize<String>         | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | 512   | ?    | 33,368.4 ns | 280.54 ns | 248.69 ns | 33,312.0 ns | 33,023.7 ns | 33,729.8 ns |  1.00 |    0.01 | 3.6058 | 0.6677 |   60624 B |        1.00 |
| TryAddGiventSize<String>         | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | 512   | ?    | 31,855.5 ns | 141.53 ns | 118.19 ns | 31,862.5 ns | 31,674.6 ns | 32,023.8 ns |  0.95 |    0.01 | 3.5497 | 0.5071 |   60712 B |        1.00 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| ContainsKeyFalse<Int32, Int32>   | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  2,137.6 ns |   2.29 ns |   2.03 ns |  2,137.5 ns |  2,133.5 ns |  2,141.5 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
| ContainsKeyFalse<Int32, Int32>   | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  2,144.4 ns |  12.83 ns |  10.72 ns |  2,141.1 ns |  2,135.6 ns |  2,171.0 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| ContainsKeyFalse<String, String> | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  6,749.0 ns |  60.43 ns |  56.52 ns |  6,725.9 ns |  6,700.9 ns |  6,892.5 ns |  1.00 |    0.01 |      - |      - |         - |          NA |
| ContainsKeyFalse<String, String> | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  6,702.5 ns |   7.91 ns |   6.61 ns |  6,702.5 ns |  6,692.5 ns |  6,714.9 ns |  0.99 |    0.01 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| ContainsKeyTrue<Int32, Int32>    | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  2,328.8 ns |   5.03 ns |   4.46 ns |  2,328.0 ns |  2,323.2 ns |  2,339.0 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
| ContainsKeyTrue<Int32, Int32>    | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  2,323.4 ns |   7.95 ns |   7.05 ns |  2,324.8 ns |  2,302.4 ns |  2,330.3 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| ContainsKeyTrue<String, String>  | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  9,051.3 ns |  12.19 ns |  10.18 ns |  9,051.3 ns |  9,036.7 ns |  9,071.9 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
| ContainsKeyTrue<String, String>  | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  9,166.5 ns |  26.97 ns |  21.06 ns |  9,163.3 ns |  9,126.9 ns |  9,215.3 ns |  1.01 |    0.00 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryGetValueFalse<Int32, Int32>   | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |    809.2 ns |   3.61 ns |   3.38 ns |    807.7 ns |    805.3 ns |    816.4 ns |  1.00 |    0.01 |      - |      - |         - |          NA |
| TryGetValueFalse<Int32, Int32>   | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |    757.5 ns |   2.76 ns |   2.31 ns |    757.8 ns |    752.8 ns |    760.9 ns |  0.94 |    0.00 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryGetValueFalse<String, String> | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  5,382.0 ns |   9.90 ns |   8.77 ns |  5,379.7 ns |  5,371.4 ns |  5,400.3 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
| TryGetValueFalse<String, String> | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  5,368.2 ns |  11.25 ns |   9.97 ns |  5,365.8 ns |  5,354.6 ns |  5,384.0 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryGetValueTrue<Int32, Int32>    | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  1,028.3 ns |   5.74 ns |   4.79 ns |  1,029.6 ns |  1,018.0 ns |  1,034.2 ns |  1.00 |    0.01 |      - |      - |         - |          NA |
| TryGetValueTrue<Int32, Int32>    | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  1,018.1 ns |   7.80 ns |   6.92 ns |  1,018.3 ns |  1,006.8 ns |  1,030.0 ns |  0.99 |    0.01 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| TryGetValueTrue<String, String>  | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  6,856.2 ns |  27.24 ns |  25.48 ns |  6,846.1 ns |  6,832.0 ns |  6,923.0 ns |  1.00 |    0.01 |      - |      - |         - |          NA |
| TryGetValueTrue<String, String>  | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  6,853.1 ns |  17.52 ns |  15.54 ns |  6,848.5 ns |  6,832.4 ns |  6,882.9 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| CtorGivenSize<Int32>             | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |    195.4 ns |   5.16 ns |   5.94 ns |    194.8 ns |    184.9 ns |    208.3 ns |  1.00 |    0.04 | 0.2658 | 0.0041 |    4448 B |        1.00 |
| CtorGivenSize<Int32>             | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |    198.8 ns |   4.99 ns |   5.75 ns |    199.7 ns |    188.8 ns |    207.0 ns |  1.02 |    0.04 | 0.2705 | 0.0041 |    4536 B |        1.02 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| CtorGivenSize<String>            | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |    200.3 ns |   4.96 ns |   5.71 ns |    201.2 ns |    188.5 ns |    209.8 ns |  1.00 |    0.04 | 0.2651 | 0.0041 |    4448 B |        1.00 |
| CtorGivenSize<String>            | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |    208.5 ns |   5.94 ns |   6.84 ns |    209.4 ns |    197.8 ns |    225.5 ns |  1.04 |    0.04 | 0.2707 | 0.0041 |    4536 B |        1.02 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| AddGivenSize<Int32>              | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 23,733.1 ns | 173.66 ns | 145.01 ns | 23,736.0 ns | 23,521.6 ns | 24,027.5 ns |  1.00 |    0.01 | 3.1250 | 0.4735 |   53192 B |        1.00 |
| AddGivenSize<Int32>              | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 21,869.5 ns | 172.93 ns | 144.41 ns | 21,836.5 ns | 21,589.6 ns | 22,208.2 ns |  0.92 |    0.01 | 3.1557 | 0.4383 |   53280 B |        1.00 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| AddGivenSize<String>             | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 34,198.4 ns | 496.09 ns | 464.04 ns | 34,005.0 ns | 33,664.4 ns | 35,291.5 ns |  1.00 |    0.02 | 3.6212 | 0.6706 |   60624 B |        1.00 |
| AddGivenSize<String>             | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 31,877.2 ns | 127.37 ns |  99.44 ns | 31,882.3 ns | 31,719.9 ns | 32,068.1 ns |  0.93 |    0.01 | 3.5497 | 0.5071 |   60712 B |        1.00 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| CtorFromCollection<Int32>        | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 21,349.9 ns | 203.97 ns | 180.81 ns | 21,357.7 ns | 21,086.6 ns | 21,701.8 ns |  1.00 |    0.01 | 2.9721 | 0.4246 |   50960 B |        1.00 |
| CtorFromCollection<Int32>        | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 20,783.7 ns | 104.29 ns |  87.09 ns | 20,788.1 ns | 20,611.7 ns | 20,929.2 ns |  0.97 |    0.01 | 2.9880 | 0.4150 |   51240 B |        1.01 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| CtorFromCollection<String>       | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 33,567.2 ns | 354.90 ns | 314.61 ns | 33,482.9 ns | 33,278.8 ns | 34,427.2 ns |  1.00 |    0.01 | 3.4722 | 0.5342 |   58568 B |        1.00 |
| CtorFromCollection<String>       | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 33,210.4 ns | 263.84 ns | 220.32 ns | 33,133.4 ns | 33,002.3 ns | 33,788.0 ns |  0.99 |    0.01 | 3.4211 | 0.5263 |   58848 B |        1.00 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| CreateAddAndClear<Int32>         | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 41,078.2 ns | 178.16 ns | 157.94 ns | 41,072.5 ns | 40,857.0 ns | 41,358.6 ns |  1.00 |    0.01 | 5.8901 | 0.8181 |   99120 B |        1.00 |
| CreateAddAndClear<Int32>         | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 37,487.7 ns | 181.74 ns | 151.76 ns | 37,522.6 ns | 37,251.5 ns | 37,796.3 ns |  0.91 |    0.00 | 6.3836 | 1.4846 |  107336 B |        1.08 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| CreateAddAndClear<String>        | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 52,386.4 ns | 423.13 ns | 375.09 ns | 52,289.1 ns | 51,567.3 ns | 53,020.4 ns |  1.00 |    0.01 | 6.4156 | 1.0348 |  108184 B |        1.00 |
| CreateAddAndClear<String>        | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 48,391.9 ns | 232.04 ns | 193.76 ns | 48,380.8 ns | 48,053.7 ns | 48,826.8 ns |  0.92 |    0.01 | 6.7724 | 1.1610 |  116400 B |        1.08 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| IterateForEach<Int32>            | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  |  6,504.7 ns |  12.54 ns |  10.47 ns |  6,504.2 ns |  6,491.8 ns |  6,532.0 ns |  1.00 |    0.00 |      - |      - |      56 B |        1.00 |
| IterateForEach<Int32>            | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  |  6,501.1 ns |   9.04 ns |   8.01 ns |  6,499.1 ns |  6,490.1 ns |  6,516.5 ns |  1.00 |    0.00 |      - |      - |      56 B |        1.00 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| IterateForEach<String>           | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 11,213.8 ns |  17.22 ns |  15.26 ns | 11,210.4 ns | 11,190.8 ns | 11,239.8 ns |  1.00 |    0.00 |      - |      - |      64 B |        1.00 |
| IterateForEach<String>           | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 11,235.4 ns |  20.17 ns |  17.88 ns | 11,230.3 ns | 11,210.9 ns | 11,270.7 ns |  1.00 |    0.00 |      - |      - |      64 B |        1.00 |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| IndexerSet<Int32>                | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 12,385.7 ns |  25.35 ns |  22.47 ns | 12,380.9 ns | 12,352.8 ns | 12,436.3 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
| IndexerSet<Int32>                | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 10,775.9 ns |  32.44 ns |  30.35 ns | 10,772.9 ns | 10,713.8 ns | 10,814.7 ns |  0.87 |    0.00 |      - |      - |         - |          NA |
|                                  |                      |            |                          |       |      |             |           |           |             |             |             |       |         |        |        |           |             |
| IndexerSet<String>               | ConcurrentDictionary | Job-DHIZMB | \CD-Baseline\corerun.exe | ?     | 512  | 19,120.0 ns |  34.56 ns |  28.86 ns | 19,117.7 ns | 19,070.8 ns | 19,181.9 ns |  1.00 |    0.00 |      - |      - |         - |          NA |
| IndexerSet<String>               | ConcurrentDictionary | Job-XIGAPM | \CD-Changed\corerun.exe  | ?     | 512  | 17,607.4 ns |  43.18 ns |  40.39 ns | 17,593.5 ns | 17,547.0 ns | 17,687.8 ns |  0.92 |    0.00 |      - |      - |         - |          NA |

// * Warnings *
Environment
  Summary -> Benchmark was executed on the virtual machine with Hyper-V hypervisor. Virtualization can affect the measurement result.

// * Hints *
Outliers
  CtorDefaultSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1           -> 1 outlier  was  removed (160.56 ns)
  CtorDefaultSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1            -> 1 outlier  was  removed (168.01 ns)
  CtorDefaultSize<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1           -> 1 outlier  was  removed (180.52 ns)
  TryAddDefaultSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1         -> 3 outliers were removed (35.95 us..36.51 us)
  TryAddDefaultSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1          -> 2 outliers were removed (34.30 us, 35.56 us)
  TryAddDefaultSize<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1        -> 2 outliers were removed (49.20 us, 49.22 us)
  TryAddDefaultSize<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1         -> 2 outliers were removed (46.25 us, 46.41 us)
  TryAddGiventSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1          -> 1 outlier  was  removed (24.90 us)
  TryAddGiventSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1           -> 1 outlier  was  removed (22.64 us)
  TryAddGiventSize<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1         -> 1 outlier  was  removed (34.48 us)
  TryAddGiventSize<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1          -> 2 outliers were removed (32.38 us, 33.04 us)
  ContainsKeyFalse<Int32, Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1   -> 1 outlier  was  removed (2.16 us)
  ContainsKeyFalse<Int32, Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1    -> 2 outliers were removed (2.18 us, 2.28 us)
  ContainsKeyFalse<String, String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1  -> 2 outliers were removed (6.78 us, 6.89 us)
  ContainsKeyTrue<Int32, Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1    -> 1 outlier  was  removed (2.37 us)
  ContainsKeyTrue<Int32, Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1     -> 1 outlier  was  removed, 2 outliers were detected (2.31 us, 2.34 us)
  ContainsKeyTrue<String, String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1  -> 2 outliers were removed (9.11 us, 9.13 us)
  ContainsKeyTrue<String, String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1   -> 3 outliers were removed (9.29 us..9.37 us)
  TryGetValueFalse<Int32, Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1    -> 2 outliers were removed (768.02 ns, 778.45 ns)
  TryGetValueFalse<String, String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1 -> 1 outlier  was  removed (5.47 us)
  TryGetValueFalse<String, String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1  -> 1 outlier  was  removed (5.44 us)
  TryGetValueTrue<Int32, Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1    -> 2 outliers were removed (1.06 us, 1.14 us)
  TryGetValueTrue<Int32, Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1     -> 1 outlier  was  removed (1.04 us)
  TryGetValueTrue<String, String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1   -> 1 outlier  was  removed (6.95 us)
  AddGivenSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1              -> 2 outliers were removed (24.30 us, 24.34 us)
  AddGivenSize<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1               -> 2 outliers were removed (22.26 us, 22.53 us)
  AddGivenSize<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1              -> 3 outliers were removed (32.46 us..33.00 us)
  CtorFromCollection<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1        -> 1 outlier  was  removed (22.65 us)
  CtorFromCollection<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1         -> 2 outliers were removed (21.10 us, 21.85 us)
  CtorFromCollection<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1       -> 1 outlier  was  removed (34.69 us)
  CtorFromCollection<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1        -> 2 outliers were removed (34.03 us, 34.27 us)
  CreateAddAndClear<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1         -> 1 outlier  was  removed (42.19 us)
  CreateAddAndClear<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1          -> 2 outliers were removed (37.98 us, 38.88 us)
  CreateAddAndClear<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1        -> 1 outlier  was  removed (54.51 us)
  CreateAddAndClear<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1         -> 2 outliers were removed (49.32 us, 50.11 us)
  IterateForEach<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1            -> 2 outliers were removed (6.54 us, 6.54 us)
  IterateForEach<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1             -> 1 outlier  was  removed (6.54 us)
  IterateForEach<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1           -> 1 outlier  was  removed (11.28 us)
  IterateForEach<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Changed\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1            -> 1 outlier  was  removed (11.38 us)
  IndexerSet<Int32>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1                -> 1 outlier  was  removed (12.64 us)
  IndexerSet<String>.ConcurrentDictionary: PowerPlanMode=00000000-0000-0000-0000-000000000000, Toolchain=\CD-Baseline\corerun.exe, IterationTime=250ms, MaxIterationCount=20, MinIterationCount=15, WarmupCount=1               -> 2 outliers were removed (19.24 us, 19.38 us)

// * Legends *
  Count       : Value of the 'Count' parameter
  Size        : Value of the 'Size' parameter
  Mean        : Arithmetic mean of all measurements
  Error       : Half of 99.9% confidence interval
  StdDev      : Standard deviation of all measurements
  Median      : Value separating the higher half of all measurements (50th percentile)
  Min         : Minimum
  Max         : Maximum
  Ratio       : Mean of the ratio distribution ([Current]/[Baseline])
  RatioSD     : Standard deviation of the ratio distribution ([Current]/[Baseline])
  Gen0        : GC Generation 0 collects per 1000 operations
  Gen1        : GC Generation 1 collects per 1000 operations
  Allocated   : Allocated memory per single operation (managed only, inclusive, 1KB = 1024B)
  Alloc Ratio : Allocated memory ratio distribution ([Current]/[Baseline])
  1 ns        : 1 Nanosecond (0.000000001 sec)

The following test case was also used to test the average number of accesses per unit of time the ConcurrentDictionary can handle with this change:

using System.Collections.Concurrent;
using System.Diagnostics;

const int KeyPoolCount = 128 << 10;
const int KeyPoolMask = KeyPoolCount - 1;

const int KeyCount = 4 << 10;
const int KeyMask = KeyCount - 1;

// Populate the key pool with unique nonzero keys. Zero is reserved for ownership.
var keyPool = new int[KeyPoolCount];
for (int i = 0; i < KeyPoolCount; i++)
    keyPool[i] = i + 1;

// Populate the keys that will exist in the CD with unique random keys from the pool. The pool entries from which the
// keys are taken are replaced with zeroes.
int rngSeed;
if (args.Length == 0)
{
    rngSeed = new Random().Next();
    Console.WriteLine($"Rng seed: {rngSeed}");
}
else
    rngSeed = (int)uint.Parse(args[0]);
var rng = new Random(rngSeed);
var keys = new int[KeyCount];
for (int i = 0; i < KeyCount; i++)
{
    int keyPoolIndex = rng.Next() & KeyPoolMask;
    int key = keyPool[keyPoolIndex];
    if (key == 0)
    {
        for (int j = keyPoolIndex + 1; ; j++)
        {
            keyPoolIndex = j & KeyPoolMask;
            key = keyPool[keyPoolIndex];
            if (key != 0)
                break;
        }
    }

    keyPool[keyPoolIndex] = 0;
    keys[i] = key;
}

// Populate the CD with the unique keys
var cd = new ConcurrentDictionary<int, int>();
for (int i = 0; i < KeyCount; i++)
{
    if (!cd.TryAdd(keys[i], 0))
        throw new InvalidOperationException();
}

int threadCount = Environment.ProcessorCount;
var threadIterationCounters = new int[(threadCount + 1) * 32];
ParameterizedThreadStart threadMain = data =>
{
    var tupleData = (Tuple<int, int>)data;
    int threadIndex = tupleData.Item1;
    int rngSeed = tupleData.Item2;
    var rng = new Random(rngSeed);
    ref int threadIterationCounter = ref threadIterationCounters[(threadIndex + 1) * 32];
    var localKeyPool = keyPool;
    var localKeys = keys;
    var localCd = cd;
    while (true)
    {
        int randomInt = rng.Next();

        // Select a new key and take ownership of the key pool entry
        int keyPoolIndex = randomInt & KeyPoolMask;
        int newKey = Interlocked.Exchange(ref localKeyPool[keyPoolIndex], 0);
        if (newKey == 0)
        {
            for (int j = keyPoolIndex + 1; ; j++)
            {
                keyPoolIndex = j & KeyPoolMask;
                newKey = Interlocked.Exchange(ref localKeyPool[keyPoolIndex], 0);
                if (newKey != 0)
                    break;
            }
        }

        // Select an existing key and take ownership of the existing key entry
        int keyIndex = randomInt & KeyMask;
        int key = Interlocked.Exchange(ref localKeys[keyIndex], 0);
        if (key == 0)
        {
            for (int j = keyIndex + 1; ; j++)
            {
                keyIndex = j & KeyMask;
                key = Interlocked.Exchange(ref localKeys[keyIndex], 0);
                if (key != 0)
                    break;
            }
        }

        if (!cd.TryRemove(key, out _) || !cd.TryAdd(newKey, 0))
            throw new InvalidOperationException();

        // Release the keys and ownerships
        localKeyPool[keyPoolIndex] = key;
        localKeys[keyIndex] = newKey;
        threadIterationCounter++;
    }
};

for (int i = 0; i < threadCount; i++)
{
    var t = new Thread(threadMain);
    t.IsBackground = true;
    t.Start(new Tuple<int, int>(i, rng.Next()));
}

var sw = new Stopwatch();

// Warmup
Thread.Sleep(6000);

// Measurements
int totalIterations = 0;
double totalDurationMs = 0;
for (int i = 0; i < 8; i++)
{
    int startIterations = 0;
    int endIterations = 0;

    for (int j = 0; j < threadCount; j++)
        startIterations += threadIterationCounters[(j + 1) * 32];
    sw.Restart();
    Thread.Sleep(1000);
    for (int j = 0; j < threadCount; j++)
        endIterations += threadIterationCounters[(j + 1) * 32];
    sw.Stop();

    int iterations = endIterations - startIterations;
    double durationMs = sw.Elapsed.TotalMilliseconds;
    Console.WriteLine($"Iterations/ms: {iterations / durationMs,10:0.0}");
    totalIterations += iterations;
    totalDurationMs += durationMs;
}

Console.WriteLine($"Average iter/ms: {totalIterations / totalDurationMs,10:0.0}");

After running the test several times, the results were the following:

Baseline Changed Diff (%)
Average iterations/ms 40486.8 40549.2 0.15%

The tests so far show that the performance of ConcurrentDictionary with these changes is relatively fine.

Copy link
Member

Choose a reason for hiding this comment

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

The CtorDefaultSize<Int32> shows exactly the same allocation before/after. How is that possible? The new version is obviously allocating one more Lock object than it was before. The Lock objects are also bigger than the System.Objects that were being used before. How should I think about this?

Copy link
Member Author

Choose a reason for hiding this comment

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

My bad, seems like I had some error while collecting the data, I updated the comment above

}

var countPerLock = new int[locks.Length];
Expand Down Expand Up @@ -440,7 +439,7 @@ private bool TryRemoveInternal(TKey key, [MaybeNullWhen(false)] out TValue value

while (true)
{
object[] locks = tables._locks;
Lock[] locks = tables._locks;
ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo);

// Do a hot read on number of items stored in the bucket. If it's empty, we can avoid
Expand Down Expand Up @@ -640,7 +639,7 @@ private bool TryUpdateInternal(Tables tables, TKey key, int? nullableHashcode, T

while (true)
{
object[] locks = tables._locks;
Lock[] locks = tables._locks;
ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo);

lock (locks[lockNo])
Expand Down Expand Up @@ -951,17 +950,16 @@ private bool TryAddInternal(Tables tables, TKey key, int? nullableHashcode, TVal

while (true)
{
object[] locks = tables._locks;
Lock[] locks = tables._locks;
ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo);

bool resizeDesired = false;
bool forceRehash = false;
bool lockTaken = false;
try
{
if (acquireLock)
{
Monitor.Enter(locks[lockNo], ref lockTaken);
locks[lockNo].Enter();
eduardo-vp marked this conversation as resolved.
Show resolved Hide resolved
}

// If the table just got resized, we may not be holding the right lock, and must retry.
Expand Down Expand Up @@ -1052,10 +1050,7 @@ private bool TryAddInternal(Tables tables, TKey key, int? nullableHashcode, TVal
}
finally
{
if (lockTaken)
{
Monitor.Exit(locks[lockNo]);
}
locks[lockNo].Exit();
}

// The fact that we got here means that we just performed an insertion. If necessary, we will grow the table.
Expand Down Expand Up @@ -2020,16 +2015,16 @@ private void GrowTable(Tables tables, bool resizeDesired, bool forceRehashIfNonR
}
}

object[] newLocks = tables._locks;
Lock[] newLocks = tables._locks;

// Add more locks
if (_growLockArray && tables._locks.Length < MaxLockNumber)
{
newLocks = new object[tables._locks.Length * 2];
newLocks = new Lock[tables._locks.Length * 2];
Array.Copy(tables._locks, newLocks, tables._locks.Length);
for (int i = tables._locks.Length; i < newLocks.Length; i++)
{
newLocks[i] = new object();
newLocks[i] = new Lock();
}
}

Expand Down Expand Up @@ -2104,11 +2099,10 @@ private void AcquireAllLocks(ref int locksAcquired)
/// </remarks>
private void AcquireFirstLock(ref int locksAcquired)
{
object[] locks = _tables._locks;
Lock[] locks = _tables._locks;
Debug.Assert(locksAcquired == 0);
Debug.Assert(!Monitor.IsEntered(locks[0]));

Monitor.Enter(locks[0]);
locks[0].Enter();
locksAcquired = 1;
}

Expand All @@ -2121,13 +2115,12 @@ private void AcquireFirstLock(ref int locksAcquired)
/// </param>
private static void AcquirePostFirstLock(Tables tables, ref int locksAcquired)
{
object[] locks = tables._locks;
Debug.Assert(Monitor.IsEntered(locks[0]));
Lock[] locks = tables._locks;
Debug.Assert(locksAcquired == 1);

for (int i = 1; i < locks.Length; i++)
{
Monitor.Enter(locks[i]);
locks[i].Enter();
locksAcquired++;
}

Expand All @@ -2140,10 +2133,10 @@ private void ReleaseLocks(int locksAcquired)
{
Debug.Assert(locksAcquired >= 0);

object[] locks = _tables._locks;
Lock[] locks = _tables._locks;
for (int i = 0; i < locksAcquired; i++)
{
Monitor.Exit(locks[i]);
locks[i].Exit();
}
}

Expand Down Expand Up @@ -2293,11 +2286,11 @@ private sealed class Tables
/// <summary>Pre-computed multiplier for use on 64-bit performing faster modulo operations.</summary>
internal readonly ulong _fastModBucketsMultiplier;
/// <summary>A set of locks, each guarding a section of the table.</summary>
internal readonly object[] _locks;
internal readonly Lock[] _locks;
/// <summary>The number of elements guarded by each lock.</summary>
internal readonly int[] _countPerLock;

internal Tables(VolatileNode[] buckets, object[] locks, int[] countPerLock, IEqualityComparer<TKey>? comparer)
internal Tables(VolatileNode[] buckets, Lock[] locks, int[] countPerLock, IEqualityComparer<TKey>? comparer)
{
Debug.Assert(typeof(TKey).IsValueType || comparer is not null);

Expand Down Expand Up @@ -2419,15 +2412,14 @@ private bool TryAdd(TAlternateKey key, TValue value, bool updateIfExists, out TV

while (true)
{
object[] locks = tables._locks;
Lock[] locks = tables._locks;
ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo);

bool resizeDesired = false;
bool forceRehash = false;
bool lockTaken = false;
try
{
Monitor.Enter(locks[lockNo], ref lockTaken);
locks[lockNo].Enter();
eduardo-vp marked this conversation as resolved.
Show resolved Hide resolved

// If the table just got resized, we may not be holding the right lock, and must retry.
// This should be a rare occurrence.
Expand Down Expand Up @@ -2523,10 +2515,7 @@ private bool TryAdd(TAlternateKey key, TValue value, bool updateIfExists, out TV
}
finally
{
if (lockTaken)
{
Monitor.Exit(locks[lockNo]);
}
locks[lockNo].Exit();
}

// The fact that we got here means that we just performed an insertion. If necessary, we will grow the table.
Expand Down Expand Up @@ -2621,7 +2610,7 @@ public bool TryRemove(TAlternateKey key, [MaybeNullWhen(false)] out TKey actualK

while (true)
{
object[] locks = tables._locks;
Lock[] locks = tables._locks;
ref Node? bucket = ref GetBucketAndLock(tables, hashcode, out uint lockNo);

// Do a hot read on number of items stored in the bucket. If it's empty, we can avoid
Expand Down
Loading