Ale*_*x D 0 ruby performance stack multithreading multicore
如果这个代码在多个内核上运行多个线程没有任何性能优势,我就不会感到头疼.但它怎么能真正运行得慢?
首先看一下代码:
class ThreadSafeStack
def initialize
@s,@m = [],Mutex.new
end
def push(value)
@m.synchronize { @s.push(value) }
end
def pop
@m.synchronize { @s.pop }
end
def peek
@m.synchronize { @s.last }
end
end
Run Code Online (Sandbox Code Playgroud)
完整的基准测试脚本位于https://github.com/alexdowad/showcase/blob/master/ruby-threads/concurrent_stack.rb.基本上,我做了一百万次推送,一百万次偷窥和一百万次流行音乐,分为1个,5个或25个线程(并行运行).
运行JRuby 1.6.5.1的4核Mac Pro的结果:
Testing ThreadSafeStack with 1 thread, iterating 1000000x each
1.575000 0.000000 1.575000 ( 1.575000)
Testing ThreadSafeStack with 5 threads, iterating 200000x each
4.838000 0.000000 4.838000 ( 4.838000)
Testing ThreadSafeStack with 25 threads, iterating 40000x each
11.409000 0.000000 11.409000 ( 11.409000)
Run Code Online (Sandbox Code Playgroud)
是什么赋予了???
编辑:可能相关的另一条信息 - 当我使用无锁堆栈(使用比较和交换操作实现)时,此基准测试确实可以更快地运行多个线程.
Bri*_*ach 10
因为......你正在同步?
任何时候只有一个线程可以执行其中任何一个...所以你不能比单个线程更快,并且你已经增加了锁的开销.
编辑以添加下面的评论,因为它是...值得添加:
锁定很昂贵.您现在有X个线程争用同一资源.我不熟悉ruby内部,告诉你他们究竟是如何实现它的,但至少在*nix上它应该是一个非常直接的pthread_mutex路径.可以在用户空间中处理无竞争锁定,但是竞争锁定需要调用内核; 这是昂贵的,为什么它慢得多,更不用说每次线程产生等待锁定时,你很可能正在做一个也很昂贵的上下文切换.
我建议你去看看Scott Meyer的幻灯片CPU缓存和你为什么关心.您特别感兴趣的是幻灯片8,它显示了向算法添加多线程的简单方法实际上需要16个物理CPU线程来匹配单个线程的性能,而2个线程比单个线程慢两倍(很像你的实验).Herb Sutter还有很多关于这个主题的文章和研讨会,而软件优化指南是一本关于这个主题的优秀书籍.当然还有多处理器编程的艺术.需要注意的是什么我上面提到的有什么相关的红宝石.这不是偶然的,主题/问题是根本性的,来自硬件.
会发生的是,即使您的互斥锁是轻量级的并且仅实现了用户空间(无法访问内核),您也会遇到CPU 缓存一致性算法.你会发现自己在看代码,每一次,在并发环境中,只是经常在读取它修改共享状态(提示:你的堆栈保护互斥正是这样的共享状态,以及堆栈本身),你应该期待相当糟糕的表现,比单个线程慢得多.基本上所有对这种共享状态的访问都必须从主RAM而不是从缓存提供,这大约慢了100倍.单个线程仅在首次访问时支付此罚分,所有后续访问将来自L1/L2缓存.
这就是为什么认真的多线程应用
如何实现这一目标的艺术因案例而异(我强烈推荐之前链接的书籍).技巧包括一次大批量工作而不是单个项目(因此争用发生的频率低得多,并且在许多项目中分摊),对共享状态(堆栈)进行分区以减少争用,使用无锁堆栈(不是实施的一项微不足道的任务).