【问题标题】:How to limit the memory used by ConcurrentDictionary如何限制 ConcurrentDictionary 使用的内存
【发布时间】:2019-06-03 13:31:05
【问题描述】:

我使用ConcurrentDictionary<String, String> 来存储大量数据(4 500 000 个条目),并且我不想使用额外的内存,所以我一开始就固定了容量。但是字典在达到指定容量之前会自动增长。

我写了一小部分代码来展示只有 500 个项目的问题,我对私有存储桶数组进行了反思,因为我没有找到提供真实容量的公共属性:

using System;
using System.Collections.Concurrent;
using System.Reflection;

namespace MemoryUsage
{
    class Program
    {
        static void Main(string[] args)
        {
            CapacityTest();
        }
        private static void CapacityTest()
        {
            int capacity = 500;
            ConcurrentDictionary<String, String> dict = new ConcurrentDictionary<string, string>(Environment.ProcessorCount, capacity);
            Console.WriteLine("{0} buckets", GetBucketCount(dict));
            for (int index = 0; index < capacity; index++)
                dict.AddOrUpdate(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), (key, value) => value);
            Console.WriteLine("{0} buckets", GetBucketCount(dict));
            Console.ReadLine();
        }
        private static int GetBucketCount(ConcurrentDictionary<string, string> dict)
        {
            object tables = dict.GetType().GetField("m_tables", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(dict); // "_tables" with .NET Core, "m_tables" with .NET Framework
            object buckets = tables.GetType().GetField("m_buckets", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(tables); // "_buckets" with .NET Core, "m_buckets" with .NET Framework
            return ((Array)buckets).Length;
        }
    }
}

显示:

500 buckets at the beginning
1003 buckets at the end

我期待500 buckets at the end。由于我知道开头的项目数,您知道避免分配额外内存的方法吗?

【问题讨论】:

  • 我不知道这一点。我认为您可以使用缓存类。 docs.microsoft.com/en-us/dotnet/framework/performance/…
  • 这似乎与值的分布有关。我用一组处理器计数 = 8 的整数 (0-7) 尝试了你的代码。然后只有 8 个桶。
  • ConcurrentDictionary 必须允许一些松弛以允许多线程的无锁操作。如果您使用12 而不是Environment.ProcessorCount,您将获得最少的桶(至少在我的系统上)。显然,这并不完全是您可以指望的,因为它还取决于您的价值分配。
  • 你说得对@JeroenMostert,用 1 代替 Environment.ProcessorCount,容量得到尊重。但我想它会降低多线程环境下的性能,我必须检查一下。

标签: c#


【解决方案1】:

这是初始容量而不仅仅是容量。所以你不能限制它。

不要重新发明轮子,只需使用MemoryCache。如果内存不足,它会自动删除项目。如果你真的想控制内存,例如使用MemoryCache.CacheMemoryLimit

【讨论】:

  • 在某些用例中,用MemoryCache 替换ConcurrentDictionary 是合适的,但肯定不是全部。特别是,我们实际上可能一直都需要所有这些键!此外,MemoryCache 在内存使用和并发性能方面更差(假设容量相同),因为它在内部包裹了一个良好的老式非泛型 Hashtable 和一个良好的老式锁。
  • @JeroenMostert 我在答案中添加了错误的链接。我的意思是这个包中的nuget.org/packages/Microsoft.Extensions.Caching.Memory MemoryCache 在内部使用ConcurrentDictionary - github.com/aspnet/caching/blob/master/src/…
  • 当然,但这根本不能解决原始问题,因为虽然您可以为此指定大小限制,但它会遇到相同的容量问题(显然,因为它使用 ConcurrentDictionary在封面下)。也就是说,即使你事先知道你有 X 个项目,你也不能让MemoryCache 只为 X 个项目消耗内存。即使您为此使用大小限制也是如此,因为这只会限制项目数量,而不是容量。
【解决方案2】:

这似乎与值的分布有关(对于锁定机制的优化或使用的树结构的特性)。我用一组处理器计数 = 8 的整数 (0-7) 尝试了你的代码。然后只有 8 个桶。

dict.AddOrUpdate(index, index, (key, value) => value);

但是当密钥乘以 2 时,在 8 次尝试 5 次后有 17 个桶。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-16
    • 2014-12-10
    • 1970-01-01
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多