Performance Clojure 1.3中的函数性能
我想知道是否有人可以帮助我在Clojure 1.3中实现这个代码片段。我正在尝试实现一个简单的函数,它接受两个向量并求和乘积 假设向量是X(大小10000个元素)和B(大小3个元素),乘积之和存储在向量Y中,数学上看起来如下: Y0=B0*X2+B1*X1+B2*X0 Y1=B0*X3+B1*X2+B2*X1 Y2=B0*X4+B1*X3+B2*X2 等等 对于本例,Y的大小最终将为9997,对应于(10000-3)。我已经将函数设置为接受任意大小的X和B 下面是代码:它基本上从X开始一次提取Performance Clojure 1.3中的函数性能,performance,clojure,Performance,Clojure,我想知道是否有人可以帮助我在Clojure 1.3中实现这个代码片段。我正在尝试实现一个简单的函数,它接受两个向量并求和乘积 假设向量是X(大小10000个元素)和B(大小3个元素),乘积之和存储在向量Y中,数学上看起来如下: Y0=B0*X2+B1*X1+B2*X0 Y1=B0*X3+B1*X2+B2*X1 Y2=B0*X4+B1*X3+B2*X2 等等 对于本例,Y的大小最终将为9997,对应于(10000-3)。我已经将函数设置为接受任意大小的X和B 下面是代码:它基本上从X开始一次提取(
(计数b)
元素,将其反转,将*
映射到b上,并对结果序列的内容求和以生成Y元素
(defn filt [b-vec x-vec]
(loop [n 0 sig x-vec result []]
(if (= n (- (count x-vec) (count b-vec)))
result
(recur (inc n) (rest sig) (conj result (->> sig
(take (count b-vec))
(reverse)
(map * b-vec)
(apply +)))))))
当X为(vec(范围1 10001))
且B为[1 2 3]
时,此函数运行大约需要6秒。我希望有人能对运行时提出改进建议,无论是算法方面的改进,还是我可能滥用的语言细节方面的改进
谢谢
另外,我已经完成了
(设置!*反射时发出警告*true)
,但没有收到任何反射警告消息。您多次使用count。以下代码仅计算一次计数
(defn filt [b-vec x-vec]
(let [bc (count b-vec) xc (count x-vec)]
(loop [n 0 sig x-vec result []]
(if (= n (- xc bc))
result
(recur (inc n) (rest sig) (conj result (->> sig
(take bc)
(reverse)
(map * b-vec)
(apply +))))))))
(time (def b (filt [1 2 3] (range 10000))))
=> "Elapsed time: 50.892536 msecs"
为了继续学习Ankur的优秀答案,您还可以避免重复调用reverse函数,这会使我们的性能更高一些
(defn filt [b-vec x-vec]
(let [bc (count b-vec) xc (count x-vec) bb-vec (reverse b-vec)]
(loop [n 0 sig x-vec result []]
(if (= n (- xc bc))
result
(recur (inc n) (rest sig) (conj result (->> sig
(take bc)
(map * bb-vec)
(apply +))))))))
如果您真的想要这种计算的最佳性能,那么应该使用数组而不是向量。阵列具有许多性能优势:
- 它们支持O(1)索引查找和写入-略优于O(log32n)向量
- 它们是可变的,因此您不需要一直构造新的数组-您只需创建一个数组作为输出缓冲区即可
- 它们在引擎盖下表示为Java数组,因此可以从JVM中内置的各种数组优化中获益
- 您可以使用基本数组(例如Java Double数组),这比使用装箱数字对象快得多
(defn filt [^doubles b-arr
^doubles x-arr]
(let [bc (count b-arr)
xc (count x-arr)
rc (inc (- xc bc))
result ^doubles (double-array rc)]
(dotimes [i rc]
(dotimes [j bc]
(aset result i (+ (aget result i) (* (aget x-arr (+ i j)) (aget b-arr j))))))
result))
也可以通过将B向量反转一次而不是将X向量的部分反转数百次来避免重复调用reverse吗?我不知道Clojure,但这似乎需要做很多额外的工作。实际上,反向调用是针对从x-vec获取的元素(即sig正在被反转)如果x-vec的大小为10000,那么我们将不得不进行3333次调用以反转我们从中获取的3个元素集,不是吗?但是如果我们只反转一次b-vec,我们应该能够避免它们。或者可能是半夜,我可能需要睡觉。是的,这可能是一个优化,这将使
Y0=B0*X2+B1*X1+B2*X0
操作到Y0=B2*X0+B1*X1+B0*X2
。。好吧,我也会把它作为第二答案贴出来。你已经得到了我的+1。我发现了一个很棒的尝试Clojure在线页面…也许我应该看看这种语言。这很有趣。所以我尝试了这个版本,并让它运行。我将b-vec和x-vec转换为如下Java数组:(def b-arr(转换为数组双b-vec))
和(def x-arr(转换为数组双x-vec))
。然而,这个版本的代码运行速度比Ankur和Ian提供的优化版本慢得多。平均而言,纯功能版本大约为25毫秒,而Java阵列版本大约为850毫秒。有什么建议吗?您需要为b-arr
和x-arr
使用基本数组。试试(def b-arr(double-array b-vec))
好的,我明白了为什么这个原生Java版本很慢。我用defn filt[^doubles b-arr^ doubles x-arr]
替换了defn filt[b-arr x-arr]
,它运行得非常快。谢谢你的回答,希望我也能把你的回答标记为接受。整个练习对我很有帮助。啊,看起来我是在你添加评论时添加的,直到我发布后才注意到。无论如何,我也尝试了你的建议,但是必须键入函数的参数来观察巨大的加速。我认为使用本机Java数组的缺点是,它会失去不可变持久数据结构的并发特性。