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)
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)) .....] .....)等。这样做的主要原因是避免盒装原语的开销,这又意味着您希望在内循环中避免的内存分配。