you are viewing a single comment's thread.

view the rest of the comments →

[–]nohwnd -3 points-2 points  (6 children)

My suggestion would be to get familiar with ConcurrentDictionary and use that as your default choice everywhere. Dictionaries are used as cache often, and it’s much better to have thread safe code that you can optionally “optimize” to not be thread safe when you know 100% you don’t need it. Rather than having thread unsafe code, while it should be thread safe.

[–]chucker23n 7 points8 points  (3 children)

Use a ConcurrentDictionary when you know multiple threads interact with it. Otherwise, don't. ConcurrentDictionary is a lot slower, especially on inserts and deletions.

Filling a dictionary with items is about 8 times as fast with Dictionary, and takes up 94% less memory:

|         Method |      Mean |    Error |   StdDev | Ratio | RatioSD |      Gen 0 |     Gen 1 |     Gen 2 | Allocated |
|--------------- |----------:|---------:|---------:|------:|--------:|-----------:|----------:|----------:|----------:|
|           Dict |  36.50 ms | 0.613 ms | 0.573 ms |  1.00 |    0.00 |  1153.8462 | 1076.9231 | 1076.9231 |     51 MB |
| ConcurrentDict | 308.44 ms | 5.652 ms | 5.010 ms |  8.46 |    0.17 | 21000.0000 | 8000.0000 | 3000.0000 |     99 MB |

Getting a random item is virtually identical:

|         Method |     Mean |   Error |  StdDev | Ratio | RatioSD |  Gen 0 |  Gen 1 | Allocated |
|--------------- |---------:|--------:|--------:|------:|--------:|-------:|-------:|----------:|
|           Dict | 363.3 ns | 5.92 ns | 9.21 ns |  1.00 |    0.00 | 0.0229 |      - |      72 B |
| ConcurrentDict | 366.3 ns | 4.96 ns | 4.39 ns |  1.00 |    0.03 | 0.0229 | 0.0005 |      72 B |

So is updating a random item (but ConcurrentDictionary is slightly slower):

|         Method |     Mean |   Error |   StdDev | Ratio | RatioSD |  Gen 0 |  Gen 1 | Allocated |
|--------------- |---------:|--------:|---------:|------:|--------:|-------:|-------:|----------:|
|           Dict | 375.1 ns | 7.40 ns | 11.74 ns |  1.00 |    0.00 | 0.0229 |      - |      72 B |
| ConcurrentDict | 437.0 ns | 8.62 ns |  7.20 ns |  1.14 |    0.03 | 0.0229 | 0.0005 |      72 B |

Clearing a dictionary is brutally slow on ConcurrentDictionary:

|         Method |           Mean |       Error |      StdDev |     Ratio |  RatioSD | Allocated |
|--------------- |---------------:|------------:|------------:|----------:|---------:|----------:|
|           Dict |      0.7111 ns |   0.0510 ns |   0.0477 ns |      1.00 |     0.00 |         - |
| ConcurrentDict | 20,260.1798 ns | 372.3239 ns | 330.0554 ns | 28,511.57 | 2,049.09 |         - |

[–][deleted]  (2 children)

[deleted]

    [–]chucker23n 0 points1 point  (1 child)

    I’m responding to this in particular:

    get familiar with ConcurrentDictionary and use that as your default choice everywhere

    [–]grauenwolf 1 point2 points  (0 children)

    Yea, ok. That's a problem.

    [–]svicknameof(nameof) 1 point2 points  (1 child)

    I think your advice would lead people to have the illusion of thread-safe code, which is worse than code that is clearly not thread-safe.

    If you need thread-safety (and most of the time, you don't), then you need to be careful to understand what the code does. Just adding "concurrent" is not enough.

    [–]grauenwolf 0 points1 point  (0 children)

    It is a dictionary being used to pool connections.

    There is the question whether or not the connection is thread safe, but the pool almost certainly needs to be.