6

我有一个超过 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;
    }
4

6 回答 6

3

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

您可以尝试:

  1. 如果可能的话,更有效地实现你的算法
  2. 切换到更快的环境/语言,例如非托管 C/C++。
于 2012-11-08T06:38:47.503 回答
1
  1. 您的批次大小 (62) 背后是否有理由?
  2. “MyMethod”方法是 IO 限制还是 CPU 限制?

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

于 2012-11-08T06:58:19.530 回答
0

根据这个博客,在集合的情况下,for 循环比 foreach 更快。尝试循环使用for. 我会帮你的。

于 2012-11-08T06:46:42.600 回答
0

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

您已经在以 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 个,并在自己的线程中处理每个组。我想这种“厚实的”并发会比非常细粒度的方法更有效。

于 2012-11-08T07:04:12.763 回答
0

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

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

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

然后将项目数除以nn是并行度 - 尝试 n = CPU 核心数,并将其与 x2 进行比较)并将每个块传递给SumChunk. 最后,总结子结果。

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

于 2012-11-08T07:13:47.837 回答
0

首先,数字只是不添加:

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

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

其次,当“优化” CPU 密集型计算时,并行化超出内核数量是没有意义的。尝试放弃这个神奇6216测试并进行基准测试 - 它实际上会运行得更快

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

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

于 2012-11-08T09:39:03.147 回答