【问题标题】:How to improve performance of ConcurrentDictionary.Count in C#如何在 C# 中提高 ConcurrentDictionary.Count 的性能
【发布时间】:2016-12-23 08:54:25
【问题描述】:

最近,我需要在SortedDictionarySortedList 之间进行选择,最终选择了SortedList

但是,现在我发现我的 C# 程序在执行 SortedList.Count 时速度变慢,我使用调用了数千次的函数/方法对其进行了检查。

通常我的程序会在 35 毫秒内调用该函数 10,000 次,但是在使用 SortedList.Count 时,它会减慢到 300-400 毫秒,基本上慢了 10 倍。

我也尝试了SortedList.Keys.Count,但这又将我的性能降低了 10 倍,超过 3000 毫秒。

I have only ~5000 keys/objects in SortedList<DateTime, object_name>。 我可以通过SortedList[date] (in 35 ms) 轻松快速地从我的排序列表中检索数据,因此我没有发现列表结构或其持有的对象有任何问题。

这种表现正常吗?

我还能使用什么来获取列表中的记录数,或者只是检查列表是否已填充? (除了添加一个单独的跟踪标志,我现在可以这样做)

更正: 对不起,我实际上正在使用: ConcurrentDictionary<string, SortedList<DateTime, string>> dict_list = new ConcurrentDictionary<string, SortedList<DateTime, string>>(); 而且我在各个地方都有不同的计数,有时检查列表中的项目,有时检查 ConcurrentDicitonary。所以这个问题适用于 ConcurrentDicitonary,我编写了快速测试代码来确认这一点,这需要 350 毫秒,而不使用并发。 这是使用 ConcurrentDicitonary 进行的测试,显示 350 毫秒:

public static void CountTest()
{
    //Create test ConcurrentDictionary
    ConcurrentDictionary<int, string> test_dict = new ConcurrentDictionary<int, string>();
    for (int i = 0; i < 50000; i++)
    {
        test_dict[i] = "ABC";
    }

    //Access .Count property 10,000 times
    int tick_count = Environment.TickCount;
    for (int i = 1; i <= 10000; i++)
    {
        int dict_count = test_dict.Count;
    }

    Console.WriteLine(string.Format("Time: {0} ms", Environment.TickCount - tick_count));
    Console.ReadKey();
}

【问题讨论】:

  • SortedList.Count 只是返回私有变量的值,不能慢。你确定你没有使用 LINQs Count() 方法吗?
  • 这是来自Reference SourceCountpublic virtual int Count { get { return _size; } }的代码。
  • 那么,您性能缓慢的原因在其他地方,而不是在 Count 调用中。
  • 如果您提供可验证的示例,也许我们可以提供帮助。
  • SortedList&lt;T&gt;.Count 的实现实际上是return _size;,因此您没有按照您认为的时间进行计时。您必须提供一个小型复制品。

标签: c# performance concurrentdictionary concurrent-collections


【解决方案1】:

这个article 建议改用这个:

dictionary.Skip(0).Count()

只要方法调用返回,计数就可能无效。例如,如果您想将计数写入日志以进行跟踪,则可以使用其他方法,例如无锁枚举器

【讨论】:

  • 是的,但是为什么呢?我最终在这里寻找对该建议的更好解释。
【解决方案2】:

嗯,ConcurrentDictionary&lt;TKey, TValue&gt; 必须同时与多个线程正常工作,因此它需要一些同步开销。

Count 属性的源代码:https://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,40c23c8c36011417

    public int Count
    {
        [SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "ConcurrencyCop just doesn't know about these locks")]
        get
        {
            int count = 0;

            int acquiredLocks = 0;
            try
            {
                // Acquire all locks
                AcquireAllLocks(ref acquiredLocks);

                // Compute the count, we allow overflow
                for (int i = 0; i < m_tables.m_countPerLock.Length; i++)
                {
                    count += m_tables.m_countPerLock[i];
                }

            }
            finally
            {
                // Release locks that have been acquired earlier
                ReleaseLocks(0, acquiredLocks);
            }

            return count;
        }
    }

看来您需要重构现有代码。由于您没有提供任何代码,我们无法告诉您可以优化什么。

【讨论】:

  • 嗯,这是一个大型系统,我在ConcurrentDictionary&lt;string, SortedList&lt;DateTime, object&gt;&gt; 中缓存来自多个来源的数据,然后每个提供者都在检查数据是否已经加载。但总的来说,我在各个地方以各种方式使用ConcurrentDictionarySortedList,了解并发含义。但是缓慢的伯爵让我失望了,我正在寻找一个最简单的替代品......
  • 请注意,我在填充和访问它们时从来没有遇到过问题,因此,除了 Count 之外,一切都表现得很好,这似乎很奇怪,几周前我添加了它,然后花了几天时间寻找我的系统的原因正在缓慢爬行。我希望其他人甚至可能没有意识到简单地使用 Count 可能会对性能产生严重影响。
  • @KonRad:我还是不明白。每个提供者都在调用Count,然后如果Count &gt; 0 它会尝试加载一些整体?
【解决方案3】:

对于性能敏感的代码,我不建议使用ConcurrentDictionary.Count 属性,因为它具有锁定实现。您可以改用Interlocked.Increment 自己进行计数。

【讨论】:

    猜你喜欢
    • 2019-07-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-12
    相关资源
    最近更新 更多