Clojure/Incanter中的快速矢量数学

Mat*_*ell 23 r vector clojure incanter

我目前正在研究Clojure和Incanter作为R的替代品.(不是我不喜欢R,但尝试使用新语言会很有趣.)我喜欢Incanter并且发现语法很吸引人,但是矢量化操作比较慢例如,R或Python.

作为一个例子,我想使用Incanter向量运算,Clojure map和R来获得向量的第一阶差分.以下是所有版本的代码和时间.如你所见,R显然更快.

Incanter和Clojure:

(use '(incanter core stats)) 
(def x (doall (sample-normal 1e7))) 
(time (def y (doall (minus (rest x) (butlast x))))) 
"Elapsed time: 16481.337 msecs" 
(time (def y (doall (map - (rest x) (butlast x))))) 
"Elapsed time: 16457.850 msecs"
Run Code Online (Sandbox Code Playgroud)

R:

rdiff <- function(x){ 
   n = length(x) 
   x[2:n] - x[1:(n-1)]} 
x = rnorm(1e7) 
system.time(rdiff(x)) 
   user  system elapsed 
  1.504   0.900   2.561
Run Code Online (Sandbox Code Playgroud)

所以我想知道有没有办法加速Incanter/Clojure中的矢量操作?还欢迎涉及使用来自Clojure的循环,Java数组和/或库的解决方案.

我还向Incanter Google小组发布了此问题,目前尚无回复.

更新:我已将Jouni的答案标记为已接受,请参阅下面的我自己的答案,我已经清理了他的代码并添加了一些基准测试.

Mat*_*ell 20

我的最终解决方案

在所有测试之后,我发现两种略有不同的方式以足够的速度进行计算.

首先我使用了diff具有不同类型返回值的函数,下面是返回向量的代码,但我还定时返回一个双数组的版本(用y替换(vec y))和Incanter.matrix(替换( vec y)与矩阵y).此函数仅基于java数组.这是基于Jouni的代码,删除了一些额外的类型提示.

另一种方法是使用Java数组进行计算,并将值存储在瞬态向量中.从时间上看,如果你不想返回和数组的功能,这比接近1快一点.这是在功能上实现的difft.

因此,选择实际上取决于您不想对数据做什么.我想一个好的选择是重载函数,以便它返回调用中使用的相同类型.实际上将java数组传递给diff而不是向量会使~1s更快.

不同功能的时间:

差异返回矢量:

(time (def y (diff x)))
"Elapsed time: 4733.259 msecs"
Run Code Online (Sandbox Code Playgroud)

diff returns Incanter.matrix:

(time (def y (diff x)))
"Elapsed time: 2599.728 msecs"
Run Code Online (Sandbox Code Playgroud)

diff返回双数组:

(time (def y (diff x)))
"Elapsed time: 1638.548 msecs"
Run Code Online (Sandbox Code Playgroud)

difft:

(time (def y (difft x)))
"Elapsed time: 3683.237 msecs"
Run Code Online (Sandbox Code Playgroud)

功能

(use 'incanter.stats)
(def x (vec (sample-normal 1e7)))

(defn diff [x]
  (let [y (double-array (dec (count x)))
        x (double-array x)] 
   (dotimes [i (dec (count x))]
     (aset y i
       (- (aget x (inc i))
                   (aget x i))))
   (vec y)))


(defn difft [x]
  (let [y (vector (range n))
        y (transient y)
        x (double-array x)]
   (dotimes [i (dec (count x))]
     (assoc! y i
       (- (aget x (inc i))
                   (aget x i))))
   (persistent! y))) 
Run Code Online (Sandbox Code Playgroud)

  • 对不起,我对Clojure的表现声称没有经验提出任何要求的人感到沮丧.请将此答案标记为正确答案.另一个会鼓励人们使用不必要的类型提示. (4认同)

Jou*_*nen 13

这是我的系统上的Java数组实现比R代码(YMMV)更快.注意启用反射警告,这在优化性能时是必不可少的,并且y上的重复类型提示(def上的那个似乎对aset没有帮助)并将所有内容都转换为原始double值(dotimes确保我是一个原始的int).

(set! *warn-on-reflection* true)
(use 'incanter.stats)
(def ^"[D" x (double-array (sample-normal 1e7)))

(time
 (do
   (def ^"[D" y (double-array (dec (count x))))
   (dotimes [i (dec (count x))]
     (aset ^"[D" y
       i
       (double (- (double (aget x (inc i)))
                  (double (aget x i))))))))
Run Code Online (Sandbox Code Playgroud)

  • 请注意,核心团队正在努力提高Clojure 1.3中的数字性能.http://combinate.us/clojure/2010/09/27/clojure/ (3认同)
  • 谢谢,你的代码也比我的系统上的R运行得快(花了400毫秒),但是如果从x开始计时是Clojure向量并使用"vec"命令将y转换回向量,它实际上需要总计(从输入向量到输出向量)4.5s以上.你的解决方案仍然比原来快3倍,更接近R!我特别欣赏类型提示这样一个很好的例子,因为这是我一直在努力的事情. (2认同)
  • @Matti Pastell:看看transiants,他们应该加速从数组到向量的转换.http://clojure.org/transients (2认同)