【问题标题】:Clojure numerical performance: What am I missing in my code?Clojure 数值性能:我的代码中缺少什么?
【发布时间】:2015-10-09 00:02:47
【问题描述】:

我试图弄清楚为什么此 Clojure 代码与其 Java 等效代码之间存在性能差异。 Clojure 代码:

(ns trigperf.core (:gen-class))

(deftype city [^String name ^double latr ^double lonr
               ^double sinlat ^double coslat ^double sinlon ^double coslon])

(defn dist [^city city1 ^city city2]
  (let [st1 (double (.sinlat city1)) st2 (double (.sinlat city2))
        ct1 (double (.coslat city1)) ct2 (double (.coslat city2))
        ln1 (double (.lonr city1))   ln2 (double (.lonr city2))]
    (Math/acos (+ (* st1 st2) (* ct1 ct2 (Math/cos (Math/abs (- ln1 ln2))))))))

(defn cost [route]
  (reduce + (map dist route (drop 1 route))))

(defn test-data []
  (take 100 (repeatedly
             #(let [latr (/ (* (- (rand 180) 90) Math/PI) 180)
                    lonr (/ (* (- (rand 360) 180) Math/PI) 180)]
                (city. "" latr lonr
                       (Math/sin latr) (Math/cos latr)
                       (Math/sin lonr) (Math/cos lonr))))))

(defn run-test []
  (let [data (test-data)] (time (dotimes [i 99999] (cost data)))))

(defn -main [& args] (run-test))

Java 等价物:

public class City {
    public String name;
    public double latr;
    public double lonr;
    public double sinlat;
    public double coslat;
    public double sinlon;
    public double coslon;

    public City(String n, double a, double b, double c, double d, double e, double f) {
        name = n; latr = a; lonr = b;
        sinlat = c; coslat = d; sinlon = e; coslon = f;
    }
}

public class Trigperf {

    public static City[] test_data() {
        City[] arr = new City[100];
        for (int c=0; c < 100; ++c) {
            double latr = (((Math.random() * 180) - 90) * Math.PI) / 180;
            double lonr = (((Math.random() * 180) - 90) * Math.PI) / 180;
            arr[c] = new City("", latr, lonr, Math.sin(latr), Math.cos(latr),
                              Math.sin(lonr), Math.cos(lonr));
        }

        return arr;
    }

    public static double dist(City city1, City city2) {
        return Math.acos((city1.sinlat * city2.sinlat) +
                         (city1.coslat * city2.coslat *
                          (Math.cos (Math.abs (city1.lonr - city2.lonr)))));
    }

    public static double cost(City[] route) {
        double acc = 0;
        for (int c=0; c < route.length - 1; ++c) {
            acc += dist(route[c], route[c + 1]);
        }
        return acc;
    }

    public static void run_test() {
        City[] data = test_data();
        long start = System.currentTimeMillis();
        for (int c=0; c < 99999; ++c) { cost(data); }
        long stop = System.currentTimeMillis();
        System.out.format( "Elapsed: %dms\n", stop - start);
    }

    public static void main(String[] args) {
        run_test();
    }

}

运行 Clojure 代码大约需要 4 秒,运行 Java 版本大约需要 2 秒。所以 Clojure 需要两倍的时间。但是我已经在我能想到的所有地方都进行了类型提示,并确保没有反射警告。如何提高 Clojure 代码的速度以接近 Java 代码的速度?

【问题讨论】:

  • 当您在 warn-on-reflection 设置为 true 的情况下运行时,它会在您加载文件时打印任何警告吗?
  • 没有。正如我所说,我已经摆脱了所有反射警告。
  • 对不起,我似乎并没有真正阅读整个问题。

标签: java performance clojure


【解决方案1】:

clojure 代码使用的数学略有不同,因为每个操作都在检查溢出,如果是这样就会抛出异常:

user> (* Long/MAX_VALUE 2)
ArithmeticException integer overflow  clojure.lang.Numbers.throwIntOverflow (Numbers.java:1501)

user> (unchecked-add Long/MAX_VALUE 2)
-9223372036854775807

因此,您可以通过关闭安全装置来多挤一点。从历史上看,clojure 的旧(现在古老)版本也会默认升级为更大的类型,后来被认为不值得花时间。还要注意/ 函数的使用,因为它会在可能的情况下计算比率,这涉及到计算 GCD,因此它可以在其中潜入一些成本:

user> (/ (int 2) (int 6))
1/3
user> (quot (int 2) (int 6))
0
user> (/ (float 2) (float 6))
0.3333333333333333

虽然这看起来不像这里的情况,因为你乘以 PI,这将使它成为双倍。

【讨论】:

    【解决方案2】:

    有很多原因。一般来说,处理 Clojure 序列不如使用 Java 数组高效。要获得明显的速度提升,请尝试以下操作:

    (defn test-data []
      (into-array city (take 100 (repeatedly ...)
    

    然后循环计算你的成本:

    (defn cost [route]
      (let [n (dec (alength ^objects route))]
        (loop [i 0
               acc 0]
          (if (< i n)
            (recur (inc i)
                   (+ acc (dist (aget ^objects route i) (aget ^objects route (inc i)))))
             acc))))
    

    这仍然不会像 Java 那样快,但它会更接近。一般来说,让 Clojure 与 Java 一样好是一门艺术。看到这个问题:

    Clojure Performance For Expensive Algorithms

    【讨论】:

    • 谢谢迭戈。我可以同意第一个建议,但我回避第二个。对我来说,使用函数式语言的全部意义在于从函数式特性中受益。
    • 这里也一样,但是好的抽象通常是以性能为代价的。当 2 倍的性能提升真的很重要时,最好的解决方案通常是将该功能移至 Java 并通过 Clojure 出色的互操作调用代码。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-04
    • 1970-01-01
    • 2020-05-11
    相关资源
    最近更新 更多