我试图弄清楚为什么这个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代码?
答案 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一样,这是一门艺术。看到这个问题: