什么是 clojure.lang.Var.getRawRoot,为什么叫它?

TJS*_*ing 4 optimization clojure

我正在编写一个函数,用于检查两个点是否可以在 2D 网格上看到对方,以进行寻路算法。在分析代码后,我发现它在 clojure.lang.Var.getRawRoot() 中花费了 60% 的时间。为什么这个函数消耗这么多时间,我可以优化它吗?

(defn line-of-sight-helper [^Maze maze [x0 y0] [x1 y1]]
  "Determines if there is a line of sight from [x0 y0] to [x1 y1] in maze."
  (let [dy (int (- y1 y0))
        dx (int (- x1 x0))
        sy (int (if (neg? dy) -1 1))
        sx (int (if (neg? dx) -1 1))
        dy (int (* sy dy))
        dx (int (* sx dx))
        bias-x (int (if (pos? sx) 0 -1))
        bias-y (int (if (pos? sy) 0 -1))
        x-long (boolean (>= dx dy))
        [u0 u1 du su bias-u] (if x-long
                               [(int x0) (int x1) dx sx bias-x]
                               [(int y0) (int y1) dy sy bias-y])
        [v0 v1 dv sv bias-v] (if x-long
                               [(int y0) (int y1) dy sy bias-y]
                               [(int x0) (int x1) dx sx bias-x])
        grid (if x-long
               #(blocked? maze [%1 %2])
               #(blocked? maze [%2 %1]))]
    (loop [u0 u0
             v0 v0
             error (int 0)]
      (if (not= u0 u1)
        (let [error (+ error dv)
              too-much-error? (> error du)
              next-blocked? (grid (+ u0 bias-u) (+ v0 bias-v))
              branch3 (and too-much-error? (not next-blocked?))
              v0 (int (if branch3
                        (+ v0 sv)
                        v0))
              error (if branch3
                      (int (- error du))
                      (int error))]
          (if (and too-much-error? next-blocked?)
            false
            (if (and (not (zero? error)) next-blocked?)
              false
              (if (and (zero? dv)
                       (grid (+ u0 bias-u)
                             v0)
                       (grid (+ u0 bias-u)
                             (- v0 1)))
                false
                (recur (int (+ u0 su))
                       v0
                       error)))))
       true))))
Run Code Online (Sandbox Code Playgroud)

mik*_*era 5

getVarRoot 发生了什么?

我真的很惊讶任何程序都在 getRawRoot() 上花费了大量时间。根据clojure.lang.Var 中的源代码,此方法所做的只是从 Var 返回单个字段:

final public Object getRawRoot(){
    return root;
}
Run Code Online (Sandbox Code Playgroud)

此外,它是一个小的 final 方法,因此应该被任何现代 JIT 编译器内联......基本上对 getRawRoot 的任何调用都应该非常快。

我怀疑您的分析器发生了一些奇怪的事情:也许它在 getRawRoot() 中添加调试代码需要很多时间。因此,我建议在没有分析器的情况下对您的代码进行基准测试,java -server并查看该函数的实际执行情况。

其他性能提示

确保您使用Clojure 1.3+,因为有一些对 var 访问的优化,您几乎肯定会希望在这种低级代码中利用这些优化。

如果我要猜测这段代码中实际上最大的瓶颈是什么,那么我认为网格函数每次被调用以检查网格方块时都会#(blocked? maze [%1 %2]) 构造一个新向量。如果您可以重构它,以便它不需要向量,然后您就可以#(blocked? maze %1 %2)直接使用,那就更好了。与简单的数学运算相比,构建新集合的成本很高,因此您希望在内部循环中谨慎地进行。

您还想确保尽可能使用原始操作,并使用(set! *unchecked-math* true). 确保您将本地变量声明为原语,因此您将需要 eg(let [u0 (int (if x-long x0 y0)) .....] .....)等。这样做的主要原因是避免盒装原语的开销,这又意味着您希望在内循环中避免的内存分配。