Multithreading 在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

我需要使用多线程进行计算。我使用SBCL,可移植性不是一个问题。我知道存在
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)。使用
      dolist
      进行简单的副作用循环,或者在使用e时使用
      loop
      。G并行迭代:

      (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)))