【问题标题】:How to optimize large size for loop如何优化大尺寸for循环
【发布时间】:2012-10-28 07:57:23
【问题描述】:

我有一个迭代次数超过 20k 的 for 循环,每次迭代大约需要两到三秒,总共大约需要 20 分钟。我如何优化这个 for 循环。我正在使用 .net3.5,所以并行 foreach 是不可能的。所以我将 200000 个号码分成小块并实现了一些线程,现在我可以将时间减少 50%。有没有其他方法可以优化这些 for 循环。

我的示例代码如下

    static double sum=0.0;
    public double AsyncTest()
    {
            List<Item> ItemsList = GetItem();//around 20k items
            int count = 0;
            bool flag = true;
            var newItemsList = ItemsList.Take(62).ToList();
            while (flag)
            {
                int j=0;
                WaitHandle[] waitHandles = new WaitHandle[62];
                foreach (Item item in newItemsList)
                {
                    var delegateInstance = new MyDelegate(MyMethod);
                    IAsyncResult asyncResult = delegateInstance.BeginInvoke(item.id, new AsyncCallback(MyAsyncResults), null);
                    waitHandles[j] = asyncResult.AsyncWaitHandle;
                    j++;
                }
                WaitHandle.WaitAll(waitHandles);
                count = count + 62;
                newItemsList = ItemsList.Skip(count).Take(62).ToList();  
            }
            return sum;
    }

    public double MyMethod(int id)
    {
        //Calculations
        return sum;
    }

    static public void MyAsyncResults(IAsyncResult iResult)
    {
        AsyncResult asyncResult = (AsyncResult) iResult;
        MyDelegate del = (MyDelegate) asyncResult.AsyncDelegate;
        double mySum = del.EndInvoke(iResult);
        sum = sum + mySum;
    }

【问题讨论】:

  • 能否优化“GetItem”以返回较小的数据集?
  • 是的,这是可能的,但我需要为每 20k 个项目执行计算。
  • MyMethod I/O 是否密集?为什么需要 2-3 秒?
  • 因为该方法中有超过 1000 行的计算。我们已经将那部分优化到了我们的水平。唯一摆在我们面前的就是通过并行计算来减少时间。
  • 1000 行计算?你要送谁去火星?

标签: c# performance .net-3.5


【解决方案1】:

可以通过各种技术减少循环次数。但是,这不会给您带来任何明显的改进,因为大量计算是在您的循环中执行的。如果您已经将其并行化以使用所有 CPU 内核,则无需做太多事情。有一定数量的计算要完成,并且有一定的计算机能力可用。你不能从你的机器上榨取比它所能提供的更多的东西。

您可以尝试:

  1. 如果可能,请更有效地实施您的算法
  2. 切换到更快的环境/语言,例如非托管 C/C++。

【讨论】:

  • 如何确保有效使用所有 CPU 内核?
【解决方案2】:
  1. 您的批次大小 (62) 有什么原因吗?
  2. “MyMethod”方法是否受 IO 限制或 CPU 限制?

您在每个周期中所做的就是等到所有批次完成,这会浪费一些周期(实际上,您是在等待所有 62 次调用完成,然后再进行下一批)。 您为什么不稍微改变一下方法,以便您仍然保持 N 个操作同时运行,但您会在 一个 执行操作完成后立即触发一个新操作?

【讨论】:

  • Ans1:我给了 62,因为等待句柄的最大大小是 64。我可以将它增加到 64 Ans2:它不受 IO 限制。但是超过 1000 行的计算需要 2 到 3 秒。
  • 但是在启动新操作之前,您实际上并不需要等待所有这些都完成。稍后我会发布一些代码。
【解决方案3】:

根据blog,在集合的情况下,for 循环比 foreach 更快。尝试使用for 循环。会有帮助的。

【讨论】:

  • 不,在这种情况下不会有任何显着差异。
  • @icepack:有什么具体原因吗?
  • @Sandeep:真的不是,除非你的循环代码编译成几条指令。这显然不是这里的情况。
【解决方案4】:

听起来你有一个 CPU 密集型 MyMethod。对于 CPU 密集型任务,您可以通过并行化获得显着改进,但这只是为了更好地利用所有 CPU 内核。除此之外,过多的并行化可能会开始损害性能——我认为这就是你正在做的事情。 (这与 I/O 密集型任务不同,您需要尽可能多地并行化。)

在我看来,你需要做的是编写另一种方法,该方法采用“块”项目(不是单个项目)并返回它们的“总和”:

double SumChunk(IEnumerable<Item> items)
{
    return items.Sum(x => MyMethod(x));
}

然后将项目数除以 nn 是并行度——尝试 n = CPU 核心数,并将其与 x2 进行比较)并通过每个块到SumChunk 的异步任务。最后,总结子结果。

另外,请注意是否有任何块在其他块之前完成。如果是这种情况,那么您的任务分布不是同质的。您需要创建较小的块(例如 300 个项目的块)并将它们传递给 SumChunk

【讨论】:

  • 我需要尝试这个场景并且需要检查我可以减少多少时间和性能。
【解决方案5】:

如果我错了,请纠正我,但在我看来,您的线程是在单个项目级别 - 我想知道这是否有点过于细化。

您已经在以 62 个项目为单位进行工作。如果您要获取这些项目并在一个线程中处理所有这些项目怎么办?即,你会有这样的东西:

void RunMyMethods(IEnumerable<Item> items)
{
    foreach(Item item in items)
    {
        var result = MyMethod(item);
        ...
    }
}

请记住,WaitHandle 对象可能比使用 Monitor 对象要慢:http://www.yoda.arachsys.com/csharp/threads/waithandles.shtml

否则,通常的建议是:分析性能以找到真正的瓶颈。在您的问题中,您说每次迭代需要 2-3 秒 - 如果有 20000 次迭代,则需要超过 20 分钟。

编辑:

如果您想最大限度地利用 CPU 时间,那么最好将 20000 个项目分成四组,每组 5000 个,并在自己的线程中处理每个组。我想这种“厚实的”并发会比非常细粒度的方法更有效。

【讨论】:

  • 您在编辑中提到的内容可能会减少更多时间。因为现在 20000/62=322 意味着我正在等待大约 322 次和大约 62 个线程操作。它可以减少到只有一次和大约 40(20k/500=40) 个线程。
  • @niknowj 抱歉,我的意思是 5000 人一组,而不是 500 人(这是一个错字)。但是,这会将您的等式更改为 20K/5K = 4 个线程。
【解决方案6】:

首先,数字只是不相加:

20k 次迭代,每次迭代大约需要两到三秒,总共大约 20 分钟

这是一个 x40 的“并行因素”——在普通机器上运行是永远无法实现的。

其次,当“优化”CPU 密集型计算时,超出内核数量的并行化毫无意义。尝试将神奇的 62 放到 16 并进行基准测试 - 它实际上会运行 更快

我在我的笔记本电脑上运行了你代码的变形恶意版本,使用Parallel.ForEach 得到了 10-20% 的改进

所以也许你可以让它运行 17 分钟而不是 20 分钟 - 这真的重要吗?

【讨论】:

  • 我需要在 1 或 2 分钟内获得所有结果。如果它只减少 20%,那么优化就没有用了。在这种情况下,我需要自己改变整个方法。
  • 是的,我会的。但现在我正在尝试所有可能的情况。
猜你喜欢
  • 2011-06-15
  • 1970-01-01
  • 2022-11-03
  • 2010-11-15
  • 2011-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-15
相关资源
最近更新 更多