Multithreading 在SBCL中执行多线程计算的正确方法 上下文
我需要使用多线程进行计算。我使用SBCL,可移植性不是一个问题。我知道存在Multithreading 在SBCL中执行多线程计算的正确方法 上下文,multithreading,common-lisp,sbcl,Multithreading,Common Lisp,Sbcl,我需要使用多线程进行计算。我使用SBCL,可移植性不是一个问题。我知道存在bordeaux线程和lparallel,但我想在特定SBCL线程实现提供的相对较低级别上实现一些东西。我需要最大的速度,即使以可读性/编程工作为代价 计算密集型操作示例 我们可以定义一个计算量足够大的函数,它将受益于多线程 (defun intensive-sqrt (x) "Dummy calculation for intensive algorithm. Approx 50 ms for 1e6 iterati
bordeaux线程
和lparallel
,但我想在特定SBCL线程实现提供的相对较低级别上实现一些东西。我需要最大的速度,即使以可读性/编程工作为代价
计算密集型操作示例
我们可以定义一个计算量足够大的函数,它将受益于多线程
(defun intensive-sqrt (x)
"Dummy calculation for intensive algorithm.
Approx 50 ms for 1e6 iterations."
(let ((y x))
(dotimes (it 1000000 t)
(if (> y 1.01d0)
(setf y (sqrt y))
(setf y (* y y y))))
y))
将每个计算映射到线程并执行
给定一个参数列表llarg
和一个函数fun
,我们要计算nthreads
结果并返回结果列表res list
。下面是我使用找到的资源得出的结论(见下文)
nthreads
不一定与llarg
的长度匹配,但是为了示例的简单性,我避免了额外的簿记。我还省略了用于优化的各种declare
我们可以使用以下方法测试多线程并比较计时:
(defparameter *test-args-sqrt-long* nil)
(dotimes (it 10000 t)
(push (list (+ 3d0 it)) *test-args-sqrt-long*))
(time (intensive-sqrt 5d0))
(time (maplist-fun-multi #'intensive-sqrt *test-args-sqrt-long* 100))
线程的数量相当高。我认为最好使用CPU所拥有的线程数,但我注意到性能下降在时间/操作方面几乎不明显。做更多的操作需要将输入列表分解成更小的部分
在2核/4线程机器上,上述代码输出:
Evaluation took:
0.029 seconds of real time
0.015625 seconds of total run time (0.015625 user, 0.000000 system)
55.17% CPU
71,972,879 processor cycles
22,151,168 bytes consed
Evaluation took:
1.415 seconds of real time
4.703125 seconds of total run time (4.437500 user, 0.265625 system)
[ Run times consist of 0.205 seconds GC time, and 4.499 seconds non-GC time. ]
332.37% CPU
3,530,632,834 processor cycles
2,215,345,584 bytes consed
什么事让我心烦
我给出的示例运行得非常好,而且非常健壮(即结果不会在线程之间混淆,我也不会遇到崩溃)。速度的提高也在那里,并且在我测试代码的机器上,计算确实使用了几个内核/线程。但有几件事我想征求意见/帮助:
llarg
和larg temp
的使用。这真的有必要吗?有没有办法避免操纵潜在的巨大列表线程列表中的顺序联接。我想,如果每个操作都需要不同的时间来完成,这将不是最优的。是否有一种方法可以在每个线程完成后连接它,而不是等待
- 根本不需要
帮助程序(因此我也将跳过其中的详细信息)。在线程函数中使用splice arglist
:apply
(lambda () (apply fun larg-temp))
- 你不需要(也不应该)索引到一个列表中,因为你的循环是二次的,每次查找都是O(n)。使用
进行简单的副作用循环,或者在使用e时使用dolist
。G并行迭代:loop
(loop :repeat nthreads :for args :in llarg :collect (sb-thread:make-thread (lambda () (apply fun args))))
- 要在创建相同长度的新列表时查看列表,其中每个元素都是从源列表中的相应元素计算出来的,请使用
:mapcar
(mapcar #'sb-thread:join-thread threads)
(defun map-args-parallel (fun arglists nthreads)
(let ((threads (loop :repeat nthreads
:for args :in arglists
:collect (sb-thread:make-thread
(lambda ()
(apply fun args))))))
(mapcar #'sb-thread:join-thread threads)))
演出
您是对的,通常只创建与ca相同数量的线程。可用的内核数。如果您总是通过创建n个线程来测试性能,然后加入它们,然后进入下一批,那么您的性能将不会有太大的差异。这是因为效率低下在于创建线程。线程的资源密集度与进程的资源密集度一样
通常要做的是创建一个线程池,其中的线程不会被加入,而是被重用。为此,您需要一些其他机制来传递参数和结果,例如。G频道(例如来自频道
)
然而请注意,e。g
lparallel
已经提供了一个pmap
功能,而且它做得很好。此类包装器库的目的不仅是为用户(程序员)提供一个良好的界面,而且是认真思考问题并明智地进行优化。我很有信心pmap
将比您的尝试快得多。谢谢您的回复。你的建议真的很有用。我没有意识到(dotimes…(elt))意味着二次查找时间,但它是有意义的。我的代码库中有很多循环需要纠正,但我应该可以获得很多速度。我将研究chanl
和lparallel:pmap
。在以前的一些测试中,我使用pmap时遇到了稳定性问题,如果我有时间再次测试,我将打开一个新线程。(sb-thread:make-thread-fun:arguments)
?在调用makethread
之前,重新绑定args怎么样?这有用吗?我忽略了一个事实,即makethread
的原型显示它确实需要更多参数<代码>使线程[sb thread]函数和键名参数短暂
。我必须对此进行测试。是否有一种方法可以在每个线程完成后加入,而不是等待?:为什么?这对主线程的执行时间没有重大影响:在所有情况下,您都将等待最慢的线程完成。主线程的执行时间是每个线程执行时间的最大值(加上簿记开销)。在这种情况下,我要执行多个批计算,我不想等待整个批处理完成后再开始另一个批处理(如果每个计算独立于所有其他计算)。但我猜答案在于,如下所示,使用频道或现有库。
(defun map-args-parallel (fun arglists nthreads)
(let ((threads (loop :repeat nthreads
:for args :in arglists
:collect (sb-thread:make-thread
(lambda ()
(apply fun args))))))
(mapcar #'sb-thread:join-thread threads)))