我最近一直在使用大量随机数生成“正态分布”钟形曲线进行一些实验。
方法很简单:
由于随机值0/1趋向于频繁出现,因此来自上方内循环的结束索引值趋于保持接近中心值。比起始值大/小得多的索引值越来越不寻常。
经过大量重复后,数组中的值呈正态分布钟形曲线的形状。但是,我正在使用的高质量随机函数arc4random_uniform()相当慢,并且需要大量迭代才能生成平滑曲线。
我想绘制10亿(十亿)个点。在主线程上运行,大约需要16个小时。
我决定重写计算代码以使用dispatch_async,并在我的8核Mac Pro上运行它。
我最终使用dispatch_group_async()提交了8个块,并使用了dispatch_group_notify()在所有块完成处理后通知程序。
为了简单起见,所有8个块均写入同一NSUInteger值数组。对数组条目之一进行读/修改写时,竞争条件的可能性很小,但是在那种情况下,这只会导致丢失一个值。我打算稍后再为数组增量添加一个锁(甚至可能在每个块中创建单独的数组,然后对其求和。)
无论如何,我将代码重构为使用dispatch_group_async(),并计算每个块中总值的1/8,然后将代码设置为可以运行。令我惊讶的是,虽然并发代码使Mac上的所有内核都用尽了,但它的 MUCH 的运行速度比单线程代码慢。
当在单线程上运行时,每秒可获得约17,800点的绘图。当使用dispatch_group_async运行时,性能下降到更像665点/秒,或者大约是的1/26。我已经更改了提交的块数-2、4或8,没关系。性能太差了。我还尝试过使用dispatch_async提交所有8个块,而没有dispatch_group。没关系。
当前没有阻塞/锁定正在进行:所有块均以全速运行。我对为什么并发代码运行速度慢感到完全困惑。
现在,代码有些混乱,因为我将其重构为可以单线程或并发运行,因此可以进行测试。
这是运行计算的代码:
randCount = 2;
#define K_USE_ASYNC 1
#if K_USE_ASYNC
dispatch_queue_t highQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//dispatch_group_async
dispatch_group_t aGroup = dispatch_group_create();
int totalJobs = 8;
for (int i = 0; i<totalJobs; i++)
{
dispatch_group_async(aGroup,
highQ,
^{
[self calculateNArrayPoints: KNumberOfEntries /totalJobs
usingRandCount: randCount];
});
}
dispatch_group_notify(aGroup,
dispatch_get_main_queue(),
allTasksDoneBlock
);
#else
[self calculateNArrayPoints: KNumberOfEntries
usingRandCount: randCount];
allTasksDoneBlock();
#endif
以及单线程版本和并发版本都使用的通用计算方法:
+ (void) calculateNArrayPoints: (NSInteger) pointCount usingRandCount: (int) randCount;
{
int entry;
int random_index;
for (entry =0; entry<pointCount; entry++)
{
static int processed = 0;
if (entry != 0 && entry%100000 == 0)
{
[self addTotTotalProcessed: processed];
processed = 0;
}
//Start with a value of 1000 (center value)
int value = 0;
//For each entry, add +/- 1 to the value 1000 times.
int limit = KPinCount;
if (randCount==2)
if (arc4random_uniform(2) !=0)
limit--;
for (random_index = 0; random_index<limit; random_index++)
{
int random_value = arc4random_uniform(randCount);
/*
if 0, value--
if 1, no change
if 2, value++
*/
if (random_value == 0)
value--;
else if (random_value == randCount-1)
value++;
}
value += 1000;
_bellCurveData[value] += 1;
//printf("\n\nfinal value = %d\n", value);
processed++;
}
}
这是一个快速而又肮脏的学习项目。它可以在Mac和iOS上运行,因此它使用共享实用程序类。实用程序类不过是类方法。没有创建实用程序方法的实例。它具有令人尴尬的全局变量数量。如果最终对代码执行了任何有用的操作,我将对其进行重构以创建实用程序单例,并将所有全局变量转换为单例上的实例变量。
目前,它是可行的,并且对globals的可怕使用不会影响结果,因此我将其保留。
使用“已处理”变量的代码只是一种确定在并发模式下运行时已计算出多少点的方法。在发现并发版本的糟糕性能之后,我添加了该代码,因此我确信这不是造成速度下降的原因。
我被困在这里。我已经编写了大量的并发代码,并且此任务是一个“embarrassingly parallel”问题,因此没有理由不应该在所有可用内核上全面运行。
是否有其他人看到任何可能导致此问题的信息,或提供其他任何见解?
最佳答案
arc4random
在修改其状态时使用关键部分。关键部分在非竞争情况下(从解锁状态更改为锁定状态)是超快的,但是在竞争情况下(尝试锁定已锁定的互斥体时),它必须调用操作系统并将线程放入睡眠,这会大大降低性能。
u_int32_t
arc4random()
{
u_int32_t rnd;
THREAD_LOCK();
arc4_check_init();
arc4_check_stir();
rnd = arc4_getword(&rs);
THREAD_UNLOCK();
return (rnd);
}
THREAD_LOCK()
定义为#define THREAD_LOCK() \
do { \
if (__isthreaded) \
_pthread_mutex_lock(&arc4random_mtx); \
} while (0)
资料来源:Arc4 random number generator for OpenBSD使其更快
您可以创建
Arc4Random
类,该类是arc4random.c的静态arc4_ *函数的包装器。然后,您将拥有一个不再具有线程安全性的随机数生成器,但可以为每个线程创建一个生成器。
关于ios - 使用dispatch_group_async的并发代码的性能比单线程版本慢很多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23685920/