随机数发生器的性能问题(proposal;已实现)
现象
创建多个线程之后,Chacha20 PRNG (arc4random_buf)只吃了一个CPU核心的计算能力。
分析
arc4random 在进行较为耗时的操作,如arc4random_buf时是持锁进行的。因此,两个线程同时调用 arc4random_buf 生成较大尺寸的数据时,调用是串行执行的。
目前想到的解法
总体来说是将目前只有一个随机状态改为可以有最多CPU数量的随机状态。将这些随机状态放在一个队列中,每次使用时取队头(如果不存在已知的随机状态,将 rs0 填入队列,如果存在且队列为空,则尝试分配新的随机状态并将其放进队列;如果队列仍为空,则等待其他线程唤醒cv_queue)。
放回的逻辑比较简单,将取出
第一步,需要将直接引用 Chacha20 状态的代码改为使用指针而不是全局变量,并将现有的这些全局变量作为 rs0。增加一对用于获取和放回随机状态的函数,其中get部分拿锁并返回rs0,put部分放锁。将原有的拿锁、放锁改为获取和放回随机状态。
第二步是改写获取和放回随机状态的函数。增加一个全局队列 rsq、 一个计数器 rscount。大致逻辑如下,获取部分:
pthread_mutex_lock(&rsq->mutex); while ((qe = TAILQ_FIRST(&rsq->queue_head)) == NULL) { if (rscount < NCPU) { /* * special case for first one. * * We use the statically allocated state * to make sure that we have at least one * available state. */ if (rscount == 0) qe = &rs0; else qe = new rstate; if (qe != NULL) { rscount++; break; } } /* All (NCPU) states are in use, sleep and wait for a wakeup */ pthread_cond_wait(&rsq->cv_queue, &rsq->mutex); } /* assert(qe != NULL); */ /* * If the queue is not empty, we must got the state from * there, and the state has to be removed from the queue. */ if (!TAILQ_EMPTY(&rsq->queue_head)) TAILQ_REMOVE(&rsq->queue_head, qe, entries); pthread_mutex_unlock(&rsq->mutex); return (qe);
放回部分:
pthread_mutex_lock(&rsq->mutex); TAILQ_INSERT_HEAD(&rsq->queue_head, qe, entries); pthread_cond_signal(&rsq->cv_queue); pthread_mutex_unlock(&rsq->mutex);
预计需要大约1-1.5个小时左右的工作量。