无锁队列,加载与卸载 CPU

Locking-Free queue, loaded vs unloded CPU

我的 CPU 是 Corei7(4 个物理内核/8 个逻辑内核)。我正在测试自由锁定队列的实现。测试是什么?我只是创建了很多线程("pusher" 和 "popper")和它们 push/pop 元素。我注意到它在加载... CPU 时工作得更快。因此,当 CPU 未相对加载时,它的工作速度较慢(相对较慢)。而且,加载后运行速度更快。

  1. 怎么理解呢?我认为这是由于 "popper" 和 "pusher" 必须争先恐后地 "head/"tail” 造成的。(我的意思是由于内存管理而增加节点)。如果有less popper/pusher then counter is lower. 但是,请注意它实际上是自由锁定的(我认为是:))

  2. 这是否意味着我应该在某些情况下让线程休眠 2 毫秒?可能是 10 毫秒(CPU 的时间太长了)。我不确定。

在核心之间弹跳高速缓存线是昂贵的。一个内核 push/pop 比 4 个内核在尝试修改同一内存时相互竞争快 4 倍,这听起来很合理。

所以听起来问题在于确定挂钟总时间或所有线程的总 CPU 时间的哪些变化告诉您您的代码是否好。


换句话说:您正在测试最大争用情况,您的线程将所有时间都花在推送和弹出上,而不做任何其他工作。在使用此队列的实际代码中,线程完成的其他工作会限制对队列的访问速率,可能会很多,因此线程相互踩脚趾的情况会少得多。 (争用可能会对 cmpxchg 循环造成重大性能影响,因为每次只有一个 CPU 会成功,而其余的每次都会重试。)

相关:Locks Aren't Slow; Lock Contention Is (Jeff Preshing) 对在高争用情况和低争用情况下使用锁的并行算法测试提出了相同的观点。


也许可以尝试对某些执行只读访问的线程进行基准测试

当大量访问都是读取时,无锁算法真正发挥作用。我想队列通常是弹出的,而不仅仅是读取的,所以这对实际使用来说可能没有意义。但我敢打赌,如果您的某些线程只是读取共享队列而不是更新它,您会看到不同的结果。 (例如以链表的形式从头到尾遍历)


另一个值得尝试的有趣的事情,在编写代码中:在从基准测试中某处的共享内存加载之前添加一条 pause 指令(_mm_pause()),以避免内存排序错误推测. (即,CPU 推测性地使用了一个在允许加载变得全局可见之前加载的值,然后当发现该值在加载应该变为全局可见时被另一个核心更改时必须回滚全局可见)。请记住,pause 在 Haswell 上休眠约 5 个周期,但在 Skylake 上休眠约 100 个周期,因此即使您在 Haswell 上的非综合基准测试中看到它的加速,离开它可能是个坏主意用于未来 CPUs.

的实际使用

请注意,pauselocked read-modify-write 指令之前没有用;他们已经在期待来自其他核心的写入。

通常情况下,您先进行松弛加载,然后进行 cmpxchg,因此我建议在加载前放置一个 pause