【问题标题】:C# Dictionary Loop EnhancmentC# 字典循环增强
【发布时间】:2011-01-25 09:03:07
【问题描述】:

我有一本包含大约 100 万个项目的字典。我不断循环扔字典:

    public void DoAllJobs()
    {
            foreach (KeyValuePair<uint, BusinessObject> p in _dictionnary)
            {
                if(p.Value.MustDoJob)
                    p.Value.DoJob();
            }
    }

执行有点长,大约 600 毫秒,我想减少它。这是禁忌:

  1. MustDoJob 值在两次调用 DoAllJobs() 之间大多保持不变
  2. MustDoJob 值的 60-70% == false
  3. MustDoJob 不时更改为 200 000 对。
  4. 某些 p.Value.DoJob() 不能同时计算(COM 对象调用)
  5. 在这里,我不需要 _dictionnary 对象的关键部分,但我在其他地方确实需要它

我想做以下事情:

  • 并行化,但由于 4,我不确定是否会有效。
  • 从 1. 和 2. 开始对字典进行排序(停止希望我找到第一个 MustDoJob == false),但我想知道 3. 会导致什么

我没有实施之前的任何想法,因为这可能是一项繁重的工作,我想在此之前研究其他选项。那么……有什么想法吗?

【问题讨论】:

    标签: c# performance dictionary


    【解决方案1】:

    改为使用 KeyValuePair 列表。这意味着您可以通过以下方式快速迭代它

    List<KeyValuePair<string,object>> list = ...;
    
    int totalItems = list.Count;
    for (int x = 0; x < totalItems; x++)
    {
        // whatever you plan to do with them, you have access to both KEY and VALUE.
    }
    

    我知道这篇文章很旧,但我一直在寻找一种方法来迭代字典,而不会增加创建枚举器(GC 和所有)的开销,或者通常是一种更快的方法来迭代它。

    【讨论】:

      【解决方案2】:

      对我来说,使用字典意味着目的是通过键查找项目,而不是访问每个项目。另一方面,循环一百万个项目的 600 毫秒是可观的。

      也许改变你的逻辑,这样你就可以直接从字典中挑选出满足条件的相关项目。

      【讨论】:

      • 循环遍历字典的内容并没有错,只要你不期望排序并且你不尝试同时修改它。
      • @Jon:对我来说,使用字典意味着目的是通过键查找项目,而不是访问每个项目。这就是我的意思。将编辑。
      • 表示something需要能够通过key找到item。这并不意味着使用相同的数据结构进行迭代是不合适的。
      • @Jon:是的,我明白这一点。如果您查看别人的代码并看到正在使用的字典,您首先会假设什么?我并不是说对它进行迭代一定是不合适的。
      【解决方案3】:

      确定循环确实是问题所在(参见TomTom's answer),我将维护一个MustDoJob 为真的项目列表——例如,当设置MustDoJob 时,将其添加到列表中,当您处理并清除标志时,将其从列表中删除。 (这可以直接通过操作标志的代码来完成,或者通过在标志更改时引发事件来完成;取决于您的需要。)然后您循环遍历列表(这只会是长度的 60-70% ),而不是字典。该列表可能包含对象本身或仅包含其在字典中的键,尽管如果它保存对象本身会更有效,因为您避免了字典查找。这确实取决于您将 200k 排队的频率,以及排队与执行的时间紧迫性。

      但同样:第 1 步是确保您是 solving the right problem

      【讨论】:

        【解决方案4】:

        我的建议是,当MustDoJob 变为真时,您的业务对象可以引发一个事件以指示它需要执行一项工作,您可以订阅该事件并将对这些对象的引用存储在一个简单的列表中,然后在调用DoAllJobs() 方法时处理该列表的内容

        【讨论】:

        • 有趣的想法,要深入研究。
        【解决方案5】:

        先尝试使用分析器。 4 让我很好奇 - 如果 COM 对象大部分时间都在使用,那么 600 毫秒可能不会那么多,然后它要么并行化,要么与之共存。

        我会首先确定 - 通过运行探查器 - 你不会在这里定位完全错误的问题。

        【讨论】:

        • 我不知道分析器是什么,有链接吗?我认为这是循环,因为我从列表中删除了 200 000 个对象(其 MustDoJob == false 因此未调用 DoJob)并且总时间显着减少(大约 100 毫秒)。至少与循环改进有关。
        • 嗯,RedGate 有一个很好的分析器 - ANTS(redgate.com,他们有试用版)。否则 Visual Dutio 分析器本身(抱歉,仅限高端版本)非常好 - 特别是 2010 年的。
        【解决方案6】:

        我的第一个建议是只使用字典中的值:

        foreach (BusinessObject> value in _dictionnary.Values)
        {
            if(value.MustDoJob)
            {
                value.DoJob();
            }
        }
        

        使用 LINQ,这可能会更容易:

        foreach (BusinessObject value in _dictionnary.Values.Where(v => v.MustDoJob))
        {
            value.DoJob();
        }
        

        这样就更清楚了。但是,目前尚不清楚还有什么实际上导致了您的问题。您需要多快能够迭代字典?我希望它已经很糟糕了...这种蛮力方法实际上有什么错误吗?用 600 毫秒迭代集合有什么影响?那是什么都不需要做任何工作的 600 毫秒吗?

        需要注意的一点:在迭代字典时,您不能更改字典的内容——无论是在这个线程中还是在另一个线程中。这意味着不添加、删除或替换键/值对。 BusinessObjectcontents 可以改变,但是 key 和 object 之间的字典关系不能改变。如果你想尽量减少不能修改字典的时间,你可以复制一份需要工作的对象的引用列表,然后对其进行迭代:

        foreach (BusinessObject value in _dictionnary.Values
                                                     .Where(v => v.MustDoJob)
                                                     .ToList())
        {
            value.DoJob();
        }
        

        【讨论】:

        • 可以复制需要工作的对象,但这不会很耗时吗? where 语句是否比手动 foreach 更有效?
        • @Toto:使用Where 只会让阅读变得更容易(IMO)——它不会让它变得更便宜。请注意,它不会复制 objects,只是复制 references。至于这将是多么耗时 - 显然这取决于需要做多少工作。不过,这可能比实际完成工作的成本要便宜得多。
        • 当您说“它可能比​​实际完成工作的成本要便宜很多。”时,您是在谈论寻找需要工作的对象的成本,是吗?如果是这样,为什么成本会便宜很多?
        • @Toto:我说的是复制参考的成本。如果您的实际工作是跨 COM 边界做任何事情,我会惊讶地发现将引用复制到新列表的成本是一个瓶颈。
        猜你喜欢
        • 1970-01-01
        • 2018-07-10
        • 1970-01-01
        • 2014-02-23
        • 2019-05-23
        • 2011-01-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多