【问题标题】:Does Clojure inlines primitive operations efficiently?Clojure 是否有效地内联原始操作?
【发布时间】:2013-06-28 16:50:12
【问题描述】:

假设我有以下 Clojure 代码:

(defn foo ^double []
  (- 
    (* 123.31
     (+ 4 5 6 (Math/sin 34.2))
     123.31) 
    123))

gen-class 是否会产生相当于编译以下 java 代码的字节码:

public static double foo(){
   return (123.31 * (4 + 5 + 6 + Math.sin(34.2)) * 123.31) - 123;
}

或者换句话说,我可以使用 Clojure 作为一种非常方便的 DSL 来生成高效的动态字节码吗?

编辑:

好的,我做了一些测试来说明我的问题:

这里是java版本:

public class FooTest {

  public static double foo(double a, double b, double c){
    return (a * (b + c + (b*c) + Math.sin(a)) * Math.log(b)) - b;
  }

  public static long benchmark(){
    long start = System.currentTimeMillis();
    for (double i = 0; i < 100000000.0; i++) { // 100 mln
      double r = foo(i, i+1, i+2);
    }
    long end = System.currentTimeMillis();
    return (end-start);
  }

  public static void main(String[] args) {
    System.out.println("Time took: "+benchmark());
  }
}

这会产生输出:耗时:39200

clojure '等效':

(defn foo ^double 
  (^double [a b c]
  (- 
    (* a
     (+ b c (* b c) (Math/sin a))
     (Math/log b)) 
    b)))

(time
  (loop [i 0.0] 
    (when (< i 100000000)   
      (foo i (+ i 1) (+ i 2))
      (recur (inc i)))))

产生:“经过的时间:121242.902 毫秒”

慢 3 倍。

现在我改写的问题是:如何构造/提示 clojure 代码,以避免代码中的函数调用,而这实际上是原始数学运算?

编辑2:

我已更改测试,因此它使用未经检查的原始数学运算符:

(defn foo ^double 
  (^double [a b c]
  (binding [*unchecked-math* true] 
    (- 
      (* a
       (+ b c (* b c) (Math/sin a))
       (Math/log b)) 
      b))))

“经过的时间:64386.187 毫秒” 所以它几乎是 java 版本的 2 倍,但仍然是 1.6 倍。

【问题讨论】:

    标签: clojure gen-class


    【解决方案1】:

    它不仅仅是 Clojure 编译器,因为 JVM 和热点 JIT 也可以优化代码。当所有值都是原始值并且任何变量都具有原始类型提示时,Clojure 编译器会生成原始数学运算。 在此之后,一旦代码在 JVM 上运行,Hotspot 优化器就会进行内联

    ps:使用或不使用gen-class 在此过程中没有区别。所有 Clojure 代码都以相同的方式编译和运行,除了 gen-class 会创建一个包含字节码的文件

    【讨论】:

    • 当你使用像unchecked-add这样的未经检查的运算时,编译器只输出原始数学运算。如果您使用+,它会生成对clojure.lang.Numbers/add 的调用,它会进行溢出检查。
    【解决方案2】:

    好的,我终于得到了与 java 相同的 Clojure 性能。 需要改变三件事:

    1. 函数参数的正确提示(之前我提示返回 值不是函数的参数)
    2. 我将装订移出 功能体。
    3. 在 微基准助手也是

    生成的代码是:

    (binding [*unchecked-math* true]   
      (defn foo ^double [^double a ^double b ^double c]  
        (- 
          (* a
             (+ b c (* b c) (Math/sin a))
             (Math/log b)) 
          b)))
    
    (binding [*unchecked-math* true]   
      (time
        (loop [i (double 0.0)] 
          (when (< i 100000000)   
            (foo i (+ i 1) (+ i 2))
            (recur (inc i))))))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-03-29
      • 1970-01-01
      • 2011-05-08
      • 1970-01-01
      • 2023-03-20
      • 2012-06-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多