我正在寻找加速冗长计算的方法(使用两个嵌套的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);
有没有办法让它更快地执行?
答案 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点)。
原始回答
从您的问题中不完全清楚性能问题是什么。有两类性能问题:
用户界面响应能力:如果您关注用户界面的响应能力,那么您绝对应该消除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
,一个微调器,所以他们知道应用程序是为他们做一些特别的事情并且它会回来了。)
通过计算本身,多线程来优化性能:如果您尝试通过制作多线程来解决更基本的性能问题,那么更多必须注意你如何做到这一点。
首先,您可能希望将所需的并发操作数限制为一些合理的数量(您绝不想冒险使用所有可用线程)。我建议你将maxConcurrentOperationCount
设置为一些小的,合理的数字(例如4或6或类似的东西)。无论如何,您在此时的回报会递减,因为该设备只有有限数量的可用内核。
其次,同样重要的是,您应该特别注意在操作之外同步更新变量(例如minY
,maxY
等)。我们假设maxY
目前为100
,您有两个并发操作,一个尝试将其设置为300
,另一个尝试将其设置为{{1} }}。但是,如果他们都确认他们已经大于当前值200
,并继续设置他们的值,那么将其设置为100
的那个恰好赢得了比赛,其他操作可以将其重置为300
,从而消除您的200
值。
如果要编写并发代码并使用单独的操作更新相同的变量,则必须非常仔细地考虑这些外部变量的同步。有关解决此问题的各种不同锁定机制的讨论,请参阅线程编程指南的Synchronization部分。或者,您可以定义另一个专用串行队列来同步值,如并发编程指南的Eliminating Lock-Based Code中所述。
最后,在考虑同步时,你总是可以退后一步,问问自己这些变量的所有这些同步的成本是否真的是必要的(因为同步时性能会受到影响,即使你没有&#39;有争议问题)。例如,尽管看起来可能违反直觉,但在这些操作期间根本不尝试更新到300
和minY
可能会更快,从而无需同步。在计算完成时,您可以放弃maxY
值范围内的这两个变量的计算,但只需等待所有操作完成,然后在整个结果集中进行最后一次迭代并计算最小和最大然后。这是一种可以根据经验进行验证的方法,您可能希望使用锁(或其他同步方法)进行验证,然后再次计算值的范围作为锁定不会在最后的单个操作是必要的。令人惊讶的是,有时在最后添加额外的循环(从而消除了同步的需要)可能会更快。
最重要的是,您通常只需要获取一段代码并使其并发,而无需特别注意这两个因素,限制您将消耗多少线程以及如果您&# 39;重新开始从多个操作中更新相同的变量,考虑如何同步这些值。即使你决定解决第二个问题,即多线程计算本身,你仍然应该考虑第一个问题,响应式UI,并且可能将这两种方法结合起来。