PPoPP 08 - FastForward
FastForward for Efficient Pipeline Parallelism http://systems.cs.colorado.edu/~moseleyt/publications/giacomoni-2008-ppopp-ff.pdf
这篇paper介绍了一种针对多核访问优化的队列, 主要的特点:
- Lock-free
- Single-producer/single-consumer
- Cache-optimized
经典的Lamport的lock-free的队列实现可以用下面的伪代码来表述(同样只适用于单一生产者/单一消费者的情形):
enqueue_nonblock(data) {
if (NEXT(head) == tail) {
return EWOULDBLOCK;
}
buffer[head] = data;
head = NEXT(head);
return 0;
}
dequeue_nonblock(data) {
(head == tail) {
return EWOULDBLOCK;
}
data = buffer[tail];
tail = NEXT(tail);
return 0;
}
这种实现尽管做到了lock-free,但是存在几个问题,首先是它只适用于sequential consistency内存模型,在relaxed consistency的内存模型里可能会出现类似于Double-Checked Lock(http://techblog.zellux.czm.cn/?p=30)的问题,当然这个问题可以通过插fence指令来解决;第二个问题是这篇paper着重解决的,就是enqueue和dequeue这两个操作都用到了tail和head这两个全局变量,导致多核在读取/修改这两个变量时的为了保证cache coherency会频繁的进行cache的更新,从而导致cache line threading,降低了效率。
接下来看FastForward的实现:
enqueue_nonblock(data) {
if (NULL != buffer[head]) {
return EWOULDBLOCK;
}
buffer[head] = data;
head = NEXT(head);
return 0;
}
dequeue_nonblock(data) {
data = buffer[tail];
if (NULL == data) {
return EWOULDBLOCK;
}
buffer[tail] = NULL;
tail = NEXT(tail);
return 0;
}
这个版本的队列实现粗看和Lamport的那个区别很小,而实际上这里解决了一个重要的问题:解除了head和tail的共享问题。dequeue操作只需要关心tail,enqueue操作只关心head,这样两个变量就变成CPU本地的资源了,不需要做任何同步。当然这个实现同样局限于sequential consistency,不过加fence保证顺序的overhead不大。最后测试中这个版本的单次操作比Lamport的快3.7倍左右,可见这样一个小小的改动对性能的提升起了举足轻重的作用。
paper后面还严格的证明了这种队列实现的正确性,即保证了消费者读出的数据的顺序和它们在生产者写入时的顺序一致,此处略去。