【问题标题】:Guile Scheme parallel forms speedupGuile Scheme 并行表单加速
【发布时间】:2018-05-13 13:02:34
【问题描述】:

我正在试验 Guile Scheme 的并行形式,我有以下代码:

(use-modules (srfi srfi-1)
             (ice-9 pretty-print)
             (ice-9 receive))

(define (busy-work limit)
  (if (> limit 0)
      (begin (sqrt (+ (expt limit limit) 1))
             (busy-work (- limit 1)))
      'done))

(define (busy-work-2 lst)
  (cond [(null? lst) 'done]
        [else
         (expt (car lst) (car lst))
         (busy-work-2 (cdr lst))]))

(define (time thunk)
  (define starting-time (current-time))
  (define res (thunk))
  (define ending-time (current-time))
  (display "elapsed time: ")
  (display (- ending-time starting-time))
  (display "s")
  (newline)
  res)


(define (partition-4 numbers)
  (define (loop numbers rc0 rc1 rc2 rc3)
    (cond [(null? numbers) (list (reverse rc0)
                                 (reverse rc1)
                                 (reverse rc2)
                                 (reverse rc3))]
          [else
           (let* ([number (car numbers)]
                  [residue (remainder number 4)])
             (cond [(= residue 0) (loop (cdr numbers)
                                        (cons number rc0)
                                        rc1
                                        rc2
                                        rc3)]
                   [(= residue 1) (loop (cdr numbers)
                                        rc0
                                        (cons number rc1)
                                        rc2
                                        rc3)]
                   [(= residue 2) (loop (cdr numbers)
                                        rc0
                                        rc1
                                        (cons number rc2)
                                        rc3)]
                   [(= residue 3) (loop (cdr numbers)
                                        rc0
                                        rc1
                                        rc2
                                        (cons number rc3))]))]))
  (loop numbers '() '() '() '()))

(或在我的实验存储库https://github.com/ZelphirKaltstahl/guile-scheme-tutorials/blob/5321470f8f3cbbdb7f64d4ed60e4b1eaf8d8f444/parallellism/utils.scm

据我所知,busy-workbusy-work-2 这两个过程是纯数字运算,带有数字列表,其中没有计算依赖于另一个。我知道时间测量可能不完全准确。

但是,我始终没有通过使用更多线程(核心,正如我在 CPU 指标中的核心使用率中看到的那样)获得预期的加速。

以下是一些示例,我希望 2 个线程完成任务的速度是 1 个核心的两倍,而 4 个核心的速度是 2 个核心的两倍。至少或多或少,因为我正在以某种方式拆分列表,这应该或多或少均匀地分布工作。

使用 4 核和parallel

(let ([residue-classes (partition-4 (iota 30000))])
  (time
   (lambda ()
     (parallel (busy-work-2 (car residue-classes))
               (busy-work-2 (cadr residue-classes))
               (busy-work-2 (caddr residue-classes))
               (busy-work-2 (cadddr residue-classes))))))

这在我的机器上大约需要 10 秒完成。有时是 9 有时是 10。

使用 par-map,它使用 4 个线程(核心)

(let ([residue-classes (partition-4 (iota 30000))])
  (time
   (lambda ()
     (par-map busy-work-2
              residue-classes))))

这在我的机器上大约需要 10 秒完成。有时是 9 有时是 10。就像parallel 一样。

使用带有 4 个线程的 n-par-map(在我的机器上)

(let ([residue-classes (partition-4 (iota 30000))])
  (time
   (lambda ()
     (n-par-map (current-processor-count)
                busy-work-2
                residue-classes))))

也是 10 秒。这里的手册 (https://www.gnu.org/software/guile/manual/html_node/Parallel-Forms.html) 说:

与上面的不同,下面描述的函数将多个线程作为参数。这使得它们本质上是不可移植的,因为指定的线程数可能与 current-processor-count 返回的可用 CPU 内核数不同(请参阅进程)。此外,这些函数在调用时会创建指定数量的线程,并在完成时终止它们,这使得它们非常昂贵。

因此,应该避免它们。

虽然我发现这种解释并没有 100% 的意义(为什么 n-par-map 不使用与 parallel 相同的预先创建的线程,如果这些线程足够多的话?就像我的 4机器?),我没有看到任何巨大的开销,我再次看到它大约在 10 秒内完成。我的猜测是,线程创建花费的时间太短了,与数字运算时的所有计算相比,它根本没有被注意到。

使用 n-par-map 和 2 个线程(核心)

(let ([residue-classes (partition-4 (iota 30000))])
  (time
   (lambda ()
     (n-par-map 2
                busy-work-2
                residue-classes))))

预期:可能会在 20 秒内完成。

结果:这在 12 秒内完成。

现在我当然在想:“4 核的运行肯定有一些巨大的开销!”。

问题:但是,当我在没有任何结果的相互依赖关系的情况下进行纯粹的数字运算时,这种开销从何而来?它是否使用了一些共享内存,从而导致内存访问成为瓶颈?

【问题讨论】:

    标签: multithreading parallel-processing scheme guile


    【解决方案1】:

    您可能正在使用具有两个超线程物理内核的机器,因此报告了 4 个 CPU。它表明这种工作负载不适合超线程。

    我在具有两个超线程物理内核的机器上得到了类似的结果。但是,对于具有 4 个物理内核的机器,我使用所有 4 个内核获得 9 秒,仅使用 2 个内核获得 16 秒,这更符合您的预期。

    【讨论】:

    • 哦,原来如此,我用的就是这样的机器!具有 4 个真实内核的 9s 与 16s 似乎也更合理。我在想,也许缓存不够,内核在某些时候使用相同的缓存,这使它们等待内存进行计算。我想再等一会儿,如果没有其他问题,请接受您的答案作为答案。
    猜你喜欢
    • 2016-01-17
    • 2016-10-16
    • 2016-10-23
    • 1970-01-01
    • 2016-11-01
    • 2013-01-07
    • 2018-12-19
    • 2020-11-06
    • 2019-07-01
    相关资源
    最近更新 更多