clojure中的二进制搜索(实现/性能)

clojure中的二进制搜索(实现/性能),clojure,Clojure,我在一个更大的程序中编写了一个二进制搜索函数,但它似乎比应该的要慢,而且分析显示了对clojure.lang.Numbers中方法的大量调用 我的理解是,Clojure可以在确定可以使用原语时使用原语。对clojure.lang.Numbers中方法的调用似乎表明它在这里没有使用原语 如果我强制循环变量为int,它会正确地抱怨recur参数不是基元。如果我也强制这些,代码会再次工作,但速度很慢。我唯一的猜测是,(quot(+low-idx-high-idx)2)没有生成原语,但我不确定从这里可以

我在一个更大的程序中编写了一个二进制搜索函数,但它似乎比应该的要慢,而且分析显示了对clojure.lang.Numbers中方法的大量调用

我的理解是,Clojure可以在确定可以使用原语时使用原语。对clojure.lang.Numbers中方法的调用似乎表明它在这里没有使用原语

如果我强制循环变量为int,它会正确地抱怨recur参数不是基元。如果我也强制这些,代码会再次工作,但速度很慢。我唯一的猜测是,
(quot(+low-idx-high-idx)2)
没有生成原语,但我不确定从这里可以走到哪里

这是我在Clojure的第一个程序,所以请随时告诉我是否有更干净/实用/Clojure的方法来做一些事情

(defn binary-search
  [coll coll-size target]
  (let [cnt (dec coll-size)]
    (loop [low-idx 0 high-idx cnt]
      (if (> low-idx high-idx)
        nil
        (let [mid-idx (quot (+ low-idx high-idx) 2) mid-val (coll mid-idx)]
          (cond
            (= mid-val target) mid-idx
            (< mid-val target) (recur (inc mid-idx) high-idx)
            (> mid-val target) (recur low-idx (dec mid-idx))
            ))))))

(defn binary-search-perf-test
  [test-size]
  (do
    (let [test-set (vec (range 1 (inc test-size))) test-set-size (count test-set)]
      (time (count (map #(binary-search2 test-set test-set-size %) test-set)))
    )))
(defn二进制搜索)
[coll coll size target]
(让[cnt(dec coll尺寸)]
(环路[低idx 0高idx cnt]
(如果(>低idx高idx)
无
(让[mid idx(QUOTE(+low idx high idx)2)mid val(coll mid idx)]
(续)
(=中间值目标)中间idx
(mid val目标)(反复出现低idx(十二月中旬idx))
))))))
(defn二进制搜索性能测试
[测试尺寸]
(做
(让[测试集(vec(范围1(包括测试大小)))测试集大小(计数测试集)]
(时间(计数(映射#(二进制搜索2测试集测试集大小%)测试集)))
)))

在Clojure 1.2.x中,您只能强制局部变量,并且它们不能跨函数调用。 从Clojure 1.3.0开始,Clojure可以在函数调用中使用首字母数字,但不能通过更高阶的函数,如
map

如果您使用的是clojure 1.3.0+,那么您应该能够使用类型提示来实现这一点

与任何clojure优化问题一样,第一步是打开
(set!*反射时警告*true)
,然后添加类型提示,直到它不再抱怨为止

user=> (set! *warn-on-reflection* true)                                          
true
user=> (defn binary-search
  [coll coll-size target]
  (let [cnt (dec coll-size)]
    (loop [low-idx 0 high-idx cnt]
      (if (> low-idx high-idx)
        nil
        (let [mid-idx (quot (+ low-idx high-idx) 2) mid-val (coll mid-idx)]
          (cond
            (= mid-val target) mid-idx
            (< mid-val target) (recur (inc mid-idx) high-idx)
            (> mid-val target) (recur low-idx (dec mid-idx))
            ))))))
NO_SOURCE_FILE:23 recur arg for primitive local: low_idx is not matching primitive, 
had: Object, needed: long
Auto-boxing loop arg: low-idx
#'user/binary-search
user=> 
user=>(设置!*反射时警告*true)
真的
用户=>(定义二进制搜索)
[coll coll size target]
(让[cnt(dec coll尺寸)]
(环路[低idx 0高idx cnt]
(如果(>低idx高idx)
无
(让[mid idx(QUOTE(+low idx high idx)2)mid val(coll mid idx)]
(续)
(=中间值目标)中间idx
(mid val目标)(反复出现低idx(十二月中旬idx))
))))))
无\u源\u文件:23基元本地的重复参数:低\u idx与基元不匹配,
had:Object,needed:long
自动装箱循环参数:低idx
#'用户/二进制搜索
用户=>
要删除此项,可以键入hint coll size参数

(defn binary-search
  [coll ^long coll-size  target]
  (let [cnt (dec coll-size)]
    (loop [low-idx 0 high-idx cnt]
      (if (> low-idx high-idx)
        nil
        (let [mid-idx (quot (+ low-idx high-idx) 2) mid-val (coll mid-idx)]
          (cond
            (= mid-val target) mid-idx
            (< mid-val target) (recur (inc mid-idx) high-idx)
            (> mid-val target) (recur low-idx (dec mid-idx))
            ))))))
(defn二进制搜索)
[coll^长coll大小目标]
(让[cnt(dec coll尺寸)]
(环路[低idx 0高idx cnt]
(如果(>低idx高idx)
无
(让[mid idx(QUOTE(+low idx high idx)2)mid val(coll mid idx)]
(续)
(=中间值目标)中间idx
(mid val目标)(反复出现低idx(十二月中旬idx))
))))))

将第10行上的自动装箱连接到coll size参数是可以理解的,因为它经过
cnt
然后
high idx
然后
mid ixd
等等,所以我通常通过键入所有提示来解决这些问题,直到找到使警告消失的提示,然后删除提示,只要它们不存在

首先,您可以使用
java.util.Collections
提供的二进制搜索实现:

(java.util.Collections/binarySearch [0 1 2 3] 2 compare)
; => 2
如果跳过
比较
,搜索速度会更快,除非集合中包含大整数,否则会中断

对于纯Clojure实现,您可以在参数向量中使用
^long
提示
coll size
——或者只在函数体的开头询问向量的大小(这是一个非常快速、恒定的时间操作),将
(quot…2)
调用替换为
(位右移…1)
并使用未选中的数学进行索引计算。通过一些额外的调整,二进制搜索可以编写如下:

(defn binary-search
  "Finds earliest occurrence of x in xs (a vector) using binary search."
  ([xs x]
     (loop [l 0 h (unchecked-dec (count xs))]
       (if (<= h (inc l))
         (cond
           (== x (xs l)) l
           (== x (xs h)) h
           :else nil)
         (let [m (unchecked-add l (bit-shift-right (unchecked-subtract h l) 1))]
           (if (< (xs m) x)
             (recur (unchecked-inc m) h)
             (recur l m)))))))

上面定义的
二进制搜索
似乎比这个
java binsearch
要多花25%的时间,谢谢。我实际上在Clojure1.2(因为这是一个“挑战”网站,他们使用1.2)和Clojure1.3中都试过。Clojure 1.2似乎与1.3具有相同的检查,但实际上是一个错误。我能够消除它们,但我仍然可以看到有很多对clojure.lang.Numbers方法的调用,这似乎意味着二进制搜索函数中的某些内容正在将int提升为其他内容,但我完全没有主意。对
c.l.Numbers
的静态方法的调用是意料之中的。这很好,真的,如果使用JIT,它们可能会被内联。(另外,委托给
c.l.Numbers
的函数也倾向于由Clojure编译器内联。)您只需要确保通过暗示达到正确的重载。我现在正在做一些测试,但是没有看到对Clojure.lang.Numbers的大量调用暗示有什么东西被装箱了吗?我看到的那些有涉及Object/Number的签名。
c.l.Numbers
有相关方法的原语重载;我希望看到那些被适当暗示的调用。在添加了大量if
(int x)
强制后,我能够摆脱非原始调用。谢谢。我对收集的大小有点谨慎,因为我还不太熟悉Clojure的所有类型。我确实在早些时候找到了好信息。我现在正在学习Clojure,试图避免调用Java
(defn java-binsearch [xs x]
  (java.util.Collections/binarySearch xs x compare))