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

Conversation

eduardo-vp
Copy link
Member

No description provided.

Copy link
Contributor

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

{
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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants