Clojure数值表现:我的代码中缺少什么?

时间:2015-10-09 00:02:48

标签: java performance clojure

我试图弄清楚为什么这个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代码?

2 个答案:

答案 0 :(得分:2)

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会使它成为双倍。

答案 1 :(得分:1)

有很多原因。通常,处理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