1

我正在寻找加快冗长计算的方法(使用两个嵌套的 for 循环),其结果将显示在图中。我尝试 NSOperationQueue 认为每个内部 for 循环都会同时运行。但显然情况并非如此,至少在我的实现中是这样。如果我删除 NSOperationQueue 调用,我会在我的情节中得到我的结果,所以我知道计算已经正确完成。

这是一个代码片段:

    NSInteger half_window, len;

    len = [myArray length];

    if (!len)
        return;

    NSOperationQueue    *queue = [[NSOperationQueue alloc] init];

    half_window = 0.5 * (self.slidingWindowSize - 1);
    numberOfPoints = len - 2 * half_window;

    double __block minY = 0;
    double __block maxY = 0;
    double __block sum, y;

    xPoints = (double *) malloc (numberOfPoints * sizeof(double));
    yPoints = (double *) malloc (numberOfPoints * sizeof(double));

    for ( NSUInteger i = half_window; i < (len - half_window); i++ )
    {
        [queue addOperationWithBlock: ^{

        sum = 0.0;

        for ( NSInteger j = -half_window; j <= half_window; j++ )
        {
            MyObject *mo = [myArray objectAtIndex: (i+j)];
            sum += mo.floatValue;
        }

        xPoints[i - half_window] = (double) i+1;

        y = (double) (sum / self.slidingWindowSize);
        yPoints[i - half_window] = y;

        if (y > maxY)
            maxY = y;

        if (y < minY)
            minY = y;
        }];

        [queue waitUntilAllOperationsAreFinished];
    }

    // update my core-plot
    self.maximumValueForXAxis = len;
    self.minimumValueForYAxis = floor(minY);
    self.maximumValueForYAxis = ceil(maxY);

    [self setUpPlotSpaceAndAxes];
    [graph reloadData];

    // cleanup
    free(xPoints);
    free(yPoints);

有没有办法让这个执行更快?

4

3 回答 3

4

添加每个项目后,您正在等待队列中的所有操作完成。

[queue waitUntilAllOperationsAreFinished];
}

// update my core-plot
self.maximumValueForXAxis = len;

应该

}
[queue waitUntilAllOperationsAreFinished];


// update my core-plot
self.maximumValueForXAxis = len;

您还在sum每个操作队列块中将变量设置为 0.0。

于 2013-05-06T22:42:33.393 回答
2

这看起来很奇怪:

for ( NSUInteger j = -half_window; j <= half_window; j++ )

假设 half_window 是正数,那么您将 unsigned int 设置为负数。我怀疑这会生成一个巨大的 unsigned int ,它将使条件失败,这意味着这个循环永远不会被计算。

但是,这不是您运行缓慢的原因。

于 2013-05-06T22:36:48.967 回答
2

修改后的答案

下面,在我的原始答案中,我解决了两种类型的性能改进,(1)通过在后台移动复杂的计算来设计响应式 UI;(2) 通过使它们成为多线程来使复杂的计算执行得更快(但这有点复杂,所以要小心)。

回想起来,我现在意识到你正在做一个移动平均,所以你for可以完全消除嵌套循环对性能的影响,从而打破了快死的结。使用伪代码,您可以执行以下操作,sum通过删除第一个点并在您进行时添加下一个点来更新(其中n表示您在移动平均线中平均多少点,例如 30 点移动平均线从你的大集合中,n是 30):

double sum = 0.0;

for (NSInteger i = 0; i < n; i++)
{
    sum += originalDataPoints[i];
}
movingAverageResult[n - 1] = sum / n;

for (NSInteger i = n; i < totalNumberOfPointsInOriginalDataSet; i++)
{
    sum = sum - originalDataPoints[i - n] + originalDataPoints[i];
    movingAverageResult[i] = sum / n;
}

这使得这是一个线性复杂性问题,应该更快。您绝对不需要将其分解为添加到某个队列中以尝试使算法运行多线程的多个操作(例如,这很好,因为您因此绕过了我在下面的第 2 点中警告您的并发症)。但是,您可以将整个算法包装为添加到调度/操作队列中的单个操作,以便根据需要异步运行您的用户界面(我在下面的第 1 点)。


原始答案

您的问题并不完全清楚性能问题是什么。有两类性能问题:

  1. 用户界面响应性:如果您关心 UI 的响应性,则绝对应该消除它,waitUntilAllOperationsAreFinished因为最终使计算与您的 UI 同步。如果您试图解决用户界面中的响应问题,您可以 (a) 删除for循环内的操作块;但是然后(b)将这两个嵌套for循环包装一个块中,您将添加到您的后台队列中。从高层次上看,代码最终看起来像:

    [queue addOperationWithBlock:^{
    
         // do all of your time consuming stuff here with
         // your nested for loops, no operations dispatched 
         // inside the for loop
    
         // when all done
    
         [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    
             // now update your UI
    
         }];
    }];
    

    注意,这里没有任何waitUntilAllOperationsAreFinished电话。响应式用户界面的目标是让它异步运行,并waitUntil...有效地使用方法使其同步,这是响应式 UI 的敌人。

    或者,您可以使用 GCD 等效项:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
         // do all of your time consuming stuff here
    
         // when all done
    
         dispatch_async(dispatch_get_main_queue(), ^{
    
             // now update your UI
    
         });
    });
    

    同样,我们正在调用dispatch_async(这相当于确保您不调用waitUntilAllOperationsAreFinished)以确保我们将此代码分派到后台,然后立即返回,以便我们的 UI 保持响应。

    执行此操作时,执行此操作的方法几乎会立即返回,从而防止 UI 在此操作期间出现卡顿/冻结。当这个操作完成时,它会相应地更新 UI。

    请注意,这假定您在单个操作中完成所有这些操作,而不是提交一堆单独的后台操作。你只需要将这个单一的操作提交到后台,它会进行复杂的计算,当它完成后,它会更新你的用户界面。同时,您的用户界面可以继续响应(让用户做其他事情,或者如果这没有意义,向用户展示一些UIActivityIndicatorView,一个微调器,以便他们知道应用程序正在为他们做一些特别的事情,并且它会马上回来)。

    然而,带回家的信息是,任何会冻结(即使是暂时)用户界面的东西都不是一个好的设计。并预先警告,如果您现有的进程需要足够长的时间,看门狗进程甚至可能会杀死您的应用程序。Apple 的建议是,至少,如果花费的时间超过几百毫秒,您应该异步执行。如果 UI 试图同时做任何其他事情(例如一些动画、一些滚动视图等),即使是几百毫秒也太长了。

  2. 通过使计算本身成为多线程来优化性能:如果您试图通过多线程来解决这个更基本的性能问题,则必须更加注意如何执行此操作。

    • 首先,您可能希望将您必须的并发操作数限制为某个合理的数量(您永远不想冒险用尽所有可用线程)。我建议你设置maxConcurrentOperationCount一些小的、合理的数字(例如 4 或 6 或类似的东西)。无论如何,此时您的收益会递减,因为该设备只有有限数量的可用内核。

    • 其次,同样重要的是,您应该特别注意在操作之外同步更新变量(例如您的minYmaxY等)。假设maxY是当前100并且您有两个并发操作,一个正在尝试将其设置为300,另一个正在尝试将其设置为200. 但是,如果他们都确认它们大于当前值,100并继续设置它们的值,如果设置它的那个300碰巧赢得了比赛,另一个操作可以将它重置回200,吹走你的300价值.

      当您想要编写具有更新相同变量的单独操作的并发代码时,您必须非常仔细地考虑这些外部变量的同步。有关解决此问题的各种不同锁定机制的讨论,请参阅线程编程指南的同步部分。或者,您可以定义另一个专用串行队列来同步值,如并发编程指南的消除基于锁的代码中所述。

      最后,在考虑同步时,您总是可以退后一步,问问自己,对这些变量进行所有这些同步的成本是否真的有必要(因为同步时会影响性能,即使您没有争用问题) . 例如,虽然这看起来违反直觉,但在这些操作期间完全不尝试更新可能会更快minYmaxY从而消除了同步的需要。你可以放弃计算这两个变量的范围y计算完成时的值,但只需等到所有操作完成,然后对整个结果集进行最后一次迭代,然后计算最小值和最大值。这是一种您可以凭经验验证的方法,您可能希望同时使用锁(或其他同步方法)尝试它,然后再次在不需要锁的最后将值的范围计算为单个操作. 令人惊讶的是,有时在最后添加额外的循环(从而消除同步的需要)会更快。

    底线是,您通常不能只获取一段串行代码并使其并发,而不特别注意这两个考虑因素,限制您将消耗多少线程以及是否要更新相同的来自多个操作的变量,请考虑您将如何同步这些值。即使您决定解决第二个问题,即多线程计算本身,您仍然应该考虑第一个问题,即响应式 UI,并可能将这两种方法结合起来。

于 2013-05-06T23:49:25.110 回答