Arrays 如何在clojure数组处理中去掉clojure/lang/RT.aset和clojure/lang/RT.intCast?
我尝试在Clojure中使复数数组的乘法尽可能快 选择的数据结构是两个元素的映射,Arrays 如何在clojure数组处理中去掉clojure/lang/RT.aset和clojure/lang/RT.intCast?,arrays,performance,clojure,clojure-java-interop,Arrays,Performance,Clojure,Clojure Java Interop,我尝试在Clojure中使复数数组的乘法尽可能快 选择的数据结构是两个元素的映射,:re和:im,每个元素都是原语双的Java本机数组,以降低内存开销 据我所知,我对基元类型的数组使用了精确的类型规范 通过这些提示,aget被转换为本机数组dloadop,但是有两个低效,循环的计数器不是int,而是long,因此每次数组被索引时,计数器都会通过调用clojure/lang/RT.intCast转换为int。而且aset也不会转换为本机操作,而是转换为对clojure/lang/RT.aset的调
:re
和:im
,每个元素都是原语双的Java本机数组,以降低内存开销
据我所知,我对基元类型的数组使用了精确的类型规范
通过这些提示,aget
被转换为本机数组dload
op,但是有两个低效,循环的计数器不是int
,而是long
,因此每次数组被索引时,计数器都会通过调用clojure/lang/RT.intCast
转换为int
。而且aset
也不会转换为本机操作,而是转换为对clojure/lang/RT.aset
的调用
另一个效率低下的问题是checkcast。它检查每个循环,确定数组实际上是双精度数组
结果是此Clojure代码的运行时间比等效Java代码的运行时间(不包括启动时间)多30%。这个函数可以在Clojure中重写以使其工作更快吗
Clojure代码,用于优化的函数是
multiply complex array
(def size 65536)
(defn get-zero-complex-array
[]
{:re (double-array size)
:im (double-array size)})
(defn multiply-complex-arrays
[a b]
(let [
a-re-array (doubles (get a :re))
a-im-array (doubles (get a :im))
b-re-array (doubles (get b :re))
b-im-array (doubles (get b :im))
res-re-array (double-array size)
res-im-array (double-array size)
]
(loop [i (int 0) size (int size)]
(if (< i size)
(let [
a-re (aget a-re-array i)
a-im (aget a-im-array i)
b-re (aget b-re-array i)
b-im (aget b-im-array i)
]
(aset res-re-array i (- (* a-re b-re) (* a-im b-im)))
(aset res-im-array i (+ (* a-re b-im) (* b-re a-im)))
(recur (unchecked-inc i) size))
{:re res-re-array :im res-im-array}))))
(let [
res (loop [i (int 0) a (get-zero-complex-array)]
(if (< i 30000)
(recur (inc i) (multiply-complex-arrays a a))
a))
]
(println (aget (get res :re) 0)))
Java代码:
class ComplexArray {
static final int SIZE = 1 << 16;
double re[];
double im[];
ComplexArray(double re[], double im[]) {
this.re = re;
this.im = im;
}
static ComplexArray getZero() {
return new ComplexArray(new double[SIZE], new double[SIZE]);
}
ComplexArray multiply(ComplexArray second) {
double resultRe[] = new double[SIZE];
double resultIm[] = new double[SIZE];
for (int i = 0; i < SIZE; i++) {
double aRe = this.re[i];
double aIm = this.im[i];
double bRe = second.re[i];
double bIm = second.im[i];
resultRe[i] = aRe * bRe - aIm * bIm;
resultIm[i] = aRe * bIm + bRe * aIm;
}
return new ComplexArray(resultRe, resultIm);
}
public static void main(String args[]) {
ComplexArray a = getZero();
for (int i = 0; i < 30000; i++) {
a = a.multiply(a);
}
System.out.println(a.re[0]);
}
}
13: iload 4
15: ldc #5 // int 65536
17: if_icmpge 92
20: aload_0
21: getfield #2 // Field re:[D
24: iload 4
26: daload
27: dstore 5
29: aload_0
30: getfield #3 // Field im:[D
33: iload 4
35: daload
36: dstore 7
38: aload_1
39: getfield #2 // Field re:[D
42: iload 4
44: daload
45: dstore 9
47: aload_1
48: getfield #3 // Field im:[D
51: iload 4
53: daload
54: dstore 11
56: aload_2
57: iload 4
59: dload 5
61: dload 9
63: dmul
64: dload 7
66: dload 11
68: dmul
69: dsub
70: dastore
71: aload_3
72: iload 4
74: dload 5
76: dload 11
78: dmul
79: dload 9
81: dload 7
83: dmul
84: dadd
85: dastore
86: iinc 4, 1
89: goto 13
您如何对该代码进行基准测试?我建议使用criterium之类的工具,或者至少在比较时间之前执行多次。当天气足够暖和时,像checkcast这样的东西应该通过JIT进行优化。我还建议使用最新的JVM、-server和-XX:+AggressiveOpts 一般来说,我发现最好不要强迫Clojure在循环中使用int,而是使用long作为循环计数器,使用
(set!*unchecked math*true)
,并让Clojure在索引到数组时将long向下转换为int。虽然这看起来像是额外的工作,但我对现代硬件/JVM/JIT的印象是,差异远小于您的预期(因为您主要使用64位整数)。另外,看起来您将size作为循环变量进行传递,但它永远不会改变-也许您这样做是为了避免与i的类型不匹配,但我只会让size(作为long)在循环之前,并对i进行长增量和比较
有时,您可以通过在循环之前进行一些操作来减少校验。虽然很容易看到代码并在不需要的时候说出来,但编译器并没有真正对此进行任何分析,而是将其留给JIT来优化(它通常非常擅长,或者在99%的代码中这并不重要)
(设置!*未选中的数学*:装箱时发出警告)
(定义^long^:常量大小65536)
(defn获取零复杂数组[]
{:re(双数组大小)
:im(双数组大小)})
(defn乘法复数数组[ab]
(让[a-re-array(double(得到a:re))
a-im-array(双倍(获得a:im))
b-re阵列(双倍(获得b:re))
b-im阵列(双倍(获得b:im))
res re数组(双数组大小)
res im阵列(双倍阵列大小)
s(长码)]
(循环[i0]
(如果(
为什么不直接使用Clojure的Java实现?@OlegTheCat有可能,我只是想知道是否有一种意识形态的方式来编写Clojure代码,Clojure编译器可以创建最佳代码。@OlegTheCat有趣的引语是“生成的代码速度完全相同”。我不知道该示例是规则(Clojure中的数组处理可能是有效的)还是异常。@SamEstep获取程序集的具体步骤:1<代码>lein新应用程序tmp2。编辑tmp/src/tmp/code.clj-placedef size
,defn get zero complex array
和defn multiply complex array
介于(:gen class))
和(defn-main
.3.lein-uberjar
4.从target/uberjar/tmp-0.1.0-SNAPSHOT.jar 5.提取tmp/core$multiply\u complex\u array.class.src
(set!*未选中的数学*true)
将intCast
调用转换为l2i
指令在使用*未检查的数学*
后时间从1.34 java变为1.22 java,在将大小移出循环并重新循环到let后(比您建议在双数组中使用它时高出2行),时间到了1.17 java,使用^long^:const
和i
类型long
时间到了1.15 java。Alex,谢谢你的宝贵建议。我在Windows下使用time
程序的输出测量墙时间。我运行了5次,得到了中值。要得到java时间的倍数,我减去执行30000次循环和在Java和Clojure中执行1个循环的时间,然后将Clojure差异除以Java差异。问题与竞争性编程有关,因此代码没有时间运行超过10秒,因此我不会在几分钟的预热后对其进行测量。是(设置!*未选中的数学*:框中警告)
全局设置?如何仅将其应用于一个函数或一个循环?*未经检查的数学*
是一个动态变量,因此它既有根绑定(false),也可以基于每个线程进行设置(此处)。它不能仅应用于一个函数或循环,除非您制作一个宏来设置和重置函数周围的值。
13: iload 4
15: ldc #5 // int 65536
17: if_icmpge 92
20: aload_0
21: getfield #2 // Field re:[D
24: iload 4
26: daload
27: dstore 5
29: aload_0
30: getfield #3 // Field im:[D
33: iload 4
35: daload
36: dstore 7
38: aload_1
39: getfield #2 // Field re:[D
42: iload 4
44: daload
45: dstore 9
47: aload_1
48: getfield #3 // Field im:[D
51: iload 4
53: daload
54: dstore 11
56: aload_2
57: iload 4
59: dload 5
61: dload 9
63: dmul
64: dload 7
66: dload 11
68: dmul
69: dsub
70: dastore
71: aload_3
72: iload 4
74: dload 5
76: dload 11
78: dmul
79: dload 9
81: dload 7
83: dmul
84: dadd
85: dastore
86: iinc 4, 1
89: goto 13
(set! *unchecked-math* :warn-on-boxed)
(def ^long ^:const size 65536)
(defn get-zero-complex-array []
{:re (double-array size)
:im (double-array size)})
(defn multiply-complex-arrays [a b]
(let [a-re-array (doubles (get a :re))
a-im-array (doubles (get a :im))
b-re-array (doubles (get b :re))
b-im-array (doubles (get b :im))
res-re-array (double-array size)
res-im-array (double-array size)
s (long size)]
(loop [i 0]
(if (< i s)
(let [a-re (aget a-re-array i)
a-im (aget a-im-array i)
b-re (aget b-re-array i)
b-im (aget b-im-array i)]
(aset res-re-array i (- (* a-re b-re) (* a-im b-im)))
(aset res-im-array i (+ (* a-re b-im) (* b-re a-im)))
(recur (inc i)))
{:re res-re-array :im res-im-array}))))
(defn compute []
(let [res (loop [i 0 a (get-zero-complex-array)]
(if (< i 30000)
(recur (inc i) (multiply-complex-arrays a a))
a))]
(aget (get res :re) 0)))