【问题标题】:Can one speed up this Chez Scheme microbenchmark?可以加速这个 Chez Scheme 微基准测试吗?
【发布时间】:2019-03-02 21:04:46
【问题描述】:

这个双循环在 Chez Scheme 中比在 C++ 中慢 50 倍(分别用 --optimize-level 3-O3 编译)

(import
  (rnrs)
  (rnrs r5rs))


(let* ((n (* 1024 16))
       (a (make-vector n))
       (acc 0))

  (do ((i 0 (+ i 1)))
    ((= i n) #f)
    (vector-set! a i (cons (cos i) (sin i))))

  (do ((i 0 (+ i 1)))
    ((= i n) #f)
    (do ((j 0 (+ j 1)))
      ((= j n) #f)
      (let ((ai (vector-ref a i))
            (aj (vector-ref a j)))
        (set! acc (+ acc (+ (* (car ai) (cdr aj))
                            (* (cdr ai) (car aj))))))))

  (write acc)
  (newline))

(exit)

#include <iostream>
#include <cmath>
#include <vector>
#include <algorithm>

typedef std::pair<double, double> pr;

typedef std::vector<pr> vec;

double loop(const vec& a)
{
    double acc = 0;
    const int n = a.size();

    for(int i = 0; i < n; ++i)
        for(int j = 0; j < n; ++j)
        {
            const pr& ai = a[i];
            const pr& aj = a[j];
            acc += ai .first * aj.second + 
                   ai.second * aj .first;
        }

    return acc;
}

int main()
{
    const int n = 1024 * 16;

    vec v(n);

    for(int i = 0; i < n; ++i)
        v[i] = pr(std::cos(i), std::sin(i));

    std::cout << loop(v) << std::endl;
}

我意识到这里的 Scheme 中的内存间接比 C++ 中的多,但性能差异仍然令人惊讶......

有没有一种简单的方法可以加快 Scheme 版本的速度? (没有将内存布局更改为完全不习惯的东西)

【问题讨论】:

    标签: scheme chez-scheme


    【解决方案1】:

    因此,虽然这些程序看起来确实相同,但它们并不相同。您在 C 版本中使用 fixnum 算术,而 Scheme 版本使用标准数字塔。要使 C 版本更像 Scheme,请尝试使用 bignum 库进行计算。

    作为一项测试,我用(rnrs arithmetic flonums)(rnrs arithmetic fixnums) 替换了算术,并将DrRacket 中的执行时间减半。我希望在任何实施中都会发生同样的情况。

    现在我的初始测试表明,C 代码的执行速度提高了大约 25 倍,而不是预期的 50 倍,通过更改为浮点运算,我发现 C 代码的执行速度大约快了 15 倍。

    我认为我可以通过使用不安全的过程来使其更快,因为 Scheme 会在运行时检查每个参数的类型,它会在每个过程之前执行操作,这在 C 版本中不会发生。作为测试,我将其更改为在我的实现中使用不安全的程序,现在它只慢了 10 倍。

    希望它对 Chez 也有帮助 :)

    编辑

    这是我修改后的源代码,速度提高了 2 倍:

    #!r6rs
    (import
     (rnrs)
     ;; import the * and + that only work on floats (which are faster, but they still check their arguments)
     (only (rnrs arithmetic flonums) fl+ fl*))
    
    
    (let* ((n (* 1024 16))
           (a (make-vector n))
           (acc 0.0)) ; We want float, lets tell Scheme about that!
    
      ;; using inexact f instead of integer i
      ;; makes every result of cos and sin inexact
      (do ((i 0 (+ i 1))
           (f 0.0 (+ f 1)))
        ((= i n) #f)
        (vector-set! a i (cons (cos f) (sin f))))
    
      (do ((i 0 (+ i 1)))
        ((= i n) #f)
        (do ((j 0 (+ j 1)))
          ((= j n) #f)
          (let ((ai (vector-ref a i))
                (aj (vector-ref a j)))
            ;; use float versions of + and *
            ;; since this is where most of the time is used
            (set! acc (fl+ acc
                           (fl+ (fl* (car ai) (cdr aj))
                                (fl* (cdr ai) (car aj))))))))
    
      (write acc)
      (newline))
    

    具体实现(锁定)只是为了说明在运行时完成的类型检查确实会产生影响,此代码的运行速度比之前的优化快 30%:

    #lang racket
    
    ;; this imports import the * and + for floats as unsafe-fl* etc. 
    (require racket/unsafe/ops)
    
    (let* ((n (* 1024 16))
           (a (make-vector n))
           (acc 0.0)) ; We want float, lets tell Scheme about that!
    
      (do ((i 0 (+ i 1))
           (f 0.0 (+ f 1)))
        ((= i n) #f)
        ;; using inexact f instead of integer i
        ;; makes every result of cos and sin inexact
        (vector-set! a i (cons (cos f) (sin f))))
    
      (do ((i 0 (+ i 1)))
        ((= i n) #f)
        (do ((j 0 (+ j 1)))
          ((= j n) #f)
          ;; We guarantee argument is a vector
          ;; and nothing wrong will happen using unsafe accessors
          (let ((ai (unsafe-vector-ref a i))
                (aj (unsafe-vector-ref a j)))
            ;; use unsafe float versions of + and *
            ;; since this is where most of the time is used
            ;; also use unsafe car/cdr as we guarantee the argument is
            ;; a pair.
            (set! acc (unsafe-fl+ acc
                                  (unsafe-fl+ (unsafe-fl* (unsafe-car ai) (unsafe-cdr aj))
                                              (unsafe-fl* (unsafe-cdr ai) (unsafe-car aj))))))))
    
      (write acc)
      (newline))
    

    我已努力保持原始代码的风格。这不是非常惯用的方案。例如。我根本不会使用set!,但它不会影响速度。

    【讨论】:

    • 你在使用-O3g++吗?
    • @MaxB 是的。 C 在0.25s 中运行并在2.5s 中编译了Scheme,而您的原始代码在6.5s 中运行(我使用time 进行了平均20 次调用)
    • 您能将您的方案代码粘贴到答案中吗?我假设您将所有算术函数调用更改为它们的非泛型版本?
    • @MaxB 当然。最后一个虽然不是Scheme,但也不代表速度提升最大。
    • C++ std::vector 最直接的类比不是scheme vector(它是异构的)而是scheme bytevector。最准确的速度比较是将 std::vector 与固定大小的整数 R6RS 字节向量操作进行比较。这几乎肯定会更快,因为它避免了方案向量中存在的指针间接。
    猜你喜欢
    • 2010-12-03
    • 2015-06-09
    • 2018-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多