使用NSOperationQueue加速长时间计算

时间:2013-05-06 22:27:02

标签: ios cocoa for-loop core-plot nsoperationqueue

我正在寻找加速冗长计算的方法(使用两个嵌套的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);

有没有办法让它更快地执行?

3 个答案:

答案 0 :(得分:4)

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

[queue waitUntilAllOperationsAreFinished];
}

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

应该是

}
[queue waitUntilAllOperationsAreFinished];


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

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

答案 1 :(得分:2)

这看起来很奇怪:

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

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

然而,这并不是导致您迟钝的原因。

答案 2 :(得分: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. 用户界面响应能力:如果您关注用户界面的响应能力,那么您绝对应该消除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,一个微调器,所以他们知道应用程序是为他们做一些特别的事情并且它会回来了。)

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

  2. 通过计算本身,多线程来优化性能:如果您尝试通过制作多线程来解决更基本的性能问题,那么更多必须注意你如何做到这一点。

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

    • 其次,同样重要的是,您应该特别注意在操作之外同步更新变量(例如minYmaxY等)。我们假设maxY目前为100,您有两个并发操作,一个尝试将其设置为300,另一个尝试将其设置为{{1} }}。但是,如果他们都确认他们已经大于当前值200,并继续设置他们的值,那么将其设置为100的那个恰好赢得了比赛,其他操作可以将其重置为300,从而消除您的200值。

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

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

    最重要的是,您通常只需要获取一段代码并使其并发,而无需特别注意这两个因素,限制您将消耗多少线程以及如果您&# 39;重新开始从多个操作中更新相同的变量,考虑如何同步这些值。即使你决定解决第二个问题,即多线程计算本身,你仍然应该考虑第一个问题,响应式UI,并且可能将这两种方法结合起来。