如何提高C#中ConcurrentDictionary.Count的性能

4
最近,我需要在使用SortedDictionarySortedList之间做出选择,并决定使用SortedList
然而,现在我发现我的C#程序在执行SortedList.Count时变得非常缓慢,我使用了一个被调用了成千上万次的函数/方法进行检查。
通常情况下,我的程序会在35毫秒内调用该函数10000次,但是使用SortedList.Count时,它变慢了10倍,变成了300-400毫秒。
我还尝试了SortedList.Keys.Count,但这使我的性能再次降低了10倍,超过了3000毫秒。
我在SortedList<DateTime, object_name>中只有约5000个键/对象。我可以通过SortedList[date] (在35毫秒内)轻松快速地从我的排序列表中检索数据,所以我没有发现任何与列表结构或其持有的对象有关的问题。
这种性能正常吗?
除了添加单独的跟踪标志(我暂时可能会这样做),还可以使用什么来获取列表中记录的数量或仅检查列表是否填充?
更正:
抱歉,实际上我正在使用: 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();
}

9
SortedList.Count仅返回私有变量的值,不可能很慢。 您确定您没有使用LINQ的Count()方法吗? - Evk
3
这是 Count 属性的代码: public virtual int Count { get { return _size; } },可以在 Reference Source 中找到。它返回 _size 的值,表示当前 SortedList 中元素的数量。 - Alessandro D'Andria
2
那么你的性能缓慢的原因在其他地方,而不是 Count 调用。 - Evk
3
如果您提供可验证的例子,或许我们就能够帮助您。 - Evk
3
SortedList<T>.Count的实现字面上是return _size;,因此你计时的不是你认为的内容。你需要提供一个简短的重现代码。 - Matthew Watson
显示剩余13条评论
3个回答

10

这篇文章建议使用以下方法:

dictionary.Skip(0).Count()

一旦方法调用返回,计数可能无效。例如,如果您想为跟踪目的将计数写入日志,您可以使用替代方法,如无锁枚举器。


是的,但为什么呢?我最终来到这里是为了寻找更好的解释。 - xr280xr
我使用BenchmarkDotNet在.NET 6中测量了这个答案,它与内置的.Count(在一个1000项的字典上)相等。 - Alex from Jitbit

2

嗯,ConcurrentDictionary<TKey, TValue>必须同时与许多线程正常工作,因此需要一些同步开销。

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<string, SortedList<DateTime, object>>缓存来自多个来源的数据,然后每个提供程序都会检查数据是否已经加载。但总体上,在各种地方以各种方式使用ConcurrentDictionarySortedList,理解并发性影响。但是慢速的Count让我感到困惑,我正在寻找最简单的替代方法... - Kon Rad
请注意,我从未遇到过填充和访问它们的问题,因此一切都表现得很好,除了 Count 外。几周前我添加了 Count,然后花了几天时间寻找系统变慢的原因。我预计其他人甚至可能没有意识到仅仅使用 Count 可能会对性能产生严重影响。 - Kon Rad
@KonRad:我还是不明白。每个提供程序都在调用“Count”,然后如果“Count>0”,它就会尝试加载一些条目? - apocalypse

0
对于对性能敏感的代码,我不建议使用ConcurrentDictionary.Count属性,因为它采用了锁定实现。你可以使用Interlocked.Increment来手动计数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接