c++ - 无锁队列,加载与卸载 CPU

标签 c++ multithreading performance x86 lock-free

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

  1. 如何理解?我认为这是由于“popper”和“pusher”必须争先恐后地“head/”tail。(我的意思是由于内存管理而增加节点)。如果 popper/pusher 较少,那么计数器较低。但是,请注意,它实际上是自由锁定的(我认为是这样:))

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

最佳答案

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

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


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

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


也许尝试用一些执行只读访问的线程进行基准测试

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


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

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

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

关于c++ - 无锁队列,加载与卸载 CPU,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39133635/

相关文章:

c++ - 更新/替换 `boost::hana::map` 中映射值的规范方法

c++ - 奇怪的编译器错误和模板继承

javascript - 嵌套的 If/Else 比 Else If 有什么优势吗?

c# - 这两种改组 IEnumerable 的算法之间是否存在性能差异?

c++ - 在 Windows 上运行 Linux Makefile(.so 文件)

c++ - 如何解决未处理的异常? (OpenGL, GLFW3)

c# - 每秒触发一个事件

java - Can 语句 n != n 在多线程环境下返回 true

c# - Object Disposed异常与多线程应用

javascript - Javascript 数字是否在 32 位浏览器中表示为 64 位数字?