我一直在寻找类似问题,但没有任何成功。我不知道如何优化cocoa中的一些代码来使用所有可用的CPU内核(我现在不想使用GPU)。下面是简单的代码示例,我的意思是:
int limA = 1000;
int limB = 1000;
unsigned short tmp;
for (int i = 0; i < 10000; i++) {
for (int a = 0; a < limA; a++) {
for (int b = 0; b < limB; b++) {
tmp = [[array objectAtIndex:(a*b)] unsignedShortValue];
c_array[a*limB+b] += tmp;
}
}
}
假设数组和c_array已正确初始化等...但正如您所看到的,如果我们有许多迭代(在这种情况下:10 ^ 10),则执行此代码需要一些时间。我想也许在几个线程中执行这个代码很简单,但是如何同步c_array呢?在objective-c中改善此类代码的时间执行的最佳方法是什么?也许它可以这样做,大多数外部for循环的迭代0-2499将在线程1和2500-4999线程2等执行...?我知道这是愚蠢的方式,但我不需要“实时”表现......任何想法?
答案 0 :(得分:8)
一些建议:
对数组进行初始传递以从其对象包装器中提取所有短路:
short *tmp_array = calloc(limA * limB, sizeof(short));
int tmp_idx = 0;
for (NSNumber *num in array) {
tmp_array[tmp_idx++] = [num unsignedShortValue];
}
这有几个好处。你从10 ^ 10方法调用到10 ^ 6,你的内部循环停止对编译器不透明(它不能“透视”方法调用),你的内部循环变小,更有可能适合指令缓存
尝试线性化访问模式。现在你正在进行“跨步”访问,因为索引每次都会成倍增加。如果您可以重新排列tmp_array
中的数据,以便顺序处理的元素在数组中也是连续的,那么您应该获得更好的性能(因为每次访问数组都会加载一个完整的缓存行,即64字节在大多数处理器上)。
从并行性中获益可能会非常棘手。您可以尝试使用:
替换外部循环dispatch_apply(10000, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) {
});
和内部循环中的+ = OSAtomicAdd
,但我怀疑无论如何你的速度将由内存访问控制,并且在混合中添加更多处理器只会导致它们踩到每个其他人的脚趾(即处理器0加载c_array[1500]
,以便它知道要添加tmp
的内容,实际加载覆盖[1500-1531]的缓存行,然后处理器1写入c_array[1512]
,使整个缓存行无效并强制重新读取它。另外,我很确定你需要在c_array中存储32位值来实现这一点,因为你将使用OSAtomicAdd32(没有OSAtomicAdd16)。
至少,如果要进行并行化,那么你需要弄清楚如何将工作划分为32个c_array
元素(即64字节)的非重叠块,以便你可以避免争用。划分数组的范围还应该让您避免需要使用原子添加操作。
(编辑)
查看an0的答案,找出并行化这一点的实用建议,而不是讨论为什么天真并行化不起作用:)
答案 1 :(得分:4)
首先,按照@ Catfish_Man的建议,除了并行性部分。
对于并行性,这是我的想法:
10000 * tmp
代替tmp
即可。 a
值严格不相交,因此可以轻松地并行化第二级循环。实际上,它也适用于b
。但是如果我们也在b
上进行并行化,那么左侧的计算单元将太小而无法分割工作负荷。 代码:
int limA = 1000;
int limB = 1000;
short *tmp_array = calloc(limA * limB, sizeof(short));
int tmp_idx = 0;
for (NSNumber *num in array) {
tmp_array[tmp_idx++] = [num unsignedShortValue];
}
dispatch_apply(limA, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t a) {
for (int b = 0; b < limB; b++) {
tmp = ;
c_array[a*limB+b] += 1000 * tmp_array[a*b];
}
});
free(tmp_array);
答案 2 :(得分:4)
首先,请关注@ Catfish_Man的建议。然后按照@ an0的建议。然后这样做:
// ...
short *tmp_array = calloc(limA * limB, sizeof(short));
unsigned short (*unsignedShortValueIMP)(id, SEL) = class_getMethodImplementation([NSNumber class], @selector(unsignedShortValue));
void * (*objectAtIndexIMP)(id, SEL, NSUInteger) = class_getMethodImplementation(array.class, @selector(objectAtIndex:));
NSUInteger n = array.count;
for (NSUInteger i = 0; i < n; ++i) {
void *obj = objectAtIndexIMP(array, @selector(objectAtIndex:), i);
tmp_array[i] = unsignedShortValueIMP((__bridge id)obj, @selector(unsignedShortValue));
}
// ...
通过将IMP
提升出Objective-C,您可以绕过消息调度机制的所有开销,并允许编译器“看到”调用;虽然这些选择器是Foundation的一部分并且无法内联,但删除额外的间接级别可以提高CPU内核中分支预测和预取机制的神圣性。此外,通过使用原始C for循环而不是Objective-C的数组枚举,并且不强制编译器上的objc_msgSend()的不透明度,允许Clang的循环展开和向量化优化器工作。
@Catfish_Man或许可以告诉我这是一个过时的优化不再值得做,但据我所知,它仍然是大量重复调用相同方法的胜利。
最后说明:我的代码假设为ARC,因此在void *
id
上使用objectAtIndex:
和桥接而非IMP
来绕过额外的隐式{{1} }/{{一对。这是邪恶的阴影hackery,禁用ARC有问题的文件是一个更好的解决方案,我应该为自己感到羞耻。