Permalink
Browse files

Remove default concurrrency level multiplier

Today when constructing a ConcurrentDictionary without specifying a concurrency level, the level defaults to Environment.ProcessorCount multiplied by a multiplier.  In my own measurements stressing writes, this multiplier has only a small effect on write throughput but a significant effect on memory consumption.  I'm removing the multiplier and leaving the default at Environment.ProcessorCount.  This provides a more reasonable compromise between wanting good write throughput while still respecting memory size in the common use case of lots of reads and few writes.  A developer can always specify their own concurrencyLevel and capacity if they want more control over memory allocations.

A benchmark that estimates the memory consumption of a Dictionary<int, string> vs a ConcurrentDictionary<int, string> with string.Empty as
values...
Before:
```
Items: 0        DSize: 80       CDSize: 976     Ratio: 12.2x
Items: 1        DSize: 192      CDSize: 1016    Ratio: 5.29166666666667x
Items: 10       DSize: 472      CDSize: 1376    Ratio: 2.91525423728814x
Items: 100      DSize: 4072     CDSize: 7552    Ratio: 1.85461689587426x
Items: 1000     DSize: 38772    CDSize: 67611   Ratio: 1.74380996595481x
```
After:
```
Items: 0        DSize: 80       CDSize: 544     Ratio: 6.8x
Items: 1        DSize: 192      CDSize: 584     Ratio: 3.04166666666667x
Items: 10       DSize: 472      CDSize: 944     Ratio: 2x
Items: 100      DSize: 4072     CDSize: 5824    Ratio: 1.43025540275049x
Items: 1000     DSize: 38772    CDSize: 53740   Ratio: 1.38605178995151x
```
  • Loading branch information...
stephentoub committed Jan 17, 2016
1 parent df1fe17 commit dd6e8994dbce7ffebb02b4b053e8ad505b7cd573
@@ -57,13 +57,6 @@ internal Tables(Node[] buckets, object[] locks, int[] countPerLock)
private readonly bool _growLockArray; // Whether to dynamically increase the size of the striped lock
private int _budget; // The maximum number of elements per lock before a resize operation is triggered
// The default concurrency level is DEFAULT_CONCURRENCY_MULTIPLIER * #CPUs. The higher the
// DEFAULT_CONCURRENCY_MULTIPLIER, the more concurrent writes can take place without interference
// and blocking, but also the more expensive operations that require all locks become (e.g. table
// resizing, ToArray, Count, etc). According to brief benchmarks that we ran, 4 seems like a good
// compromise.
private const int DEFAULT_CONCURRENCY_MULTIPLIER = 4;
// The default capacity, i.e. the initial # of buckets. When choosing this value, we are making
// a trade-off between the size of a very small dictionary, and the number of resizes when
// constructing a large dictionary. Also, the capacity should not be divisible by a small prime.
@@ -1757,7 +1750,7 @@ private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int l
/// </summary>
private static int DefaultConcurrencyLevel
{
get { return DEFAULT_CONCURRENCY_MULTIPLIER * PlatformHelper.ProcessorCount; }
get { return PlatformHelper.ProcessorCount; }
}
/// <summary>

0 comments on commit dd6e899

Please sign in to comment.