我正在编写一个函数来检查两个点是否可以在2D网格上相互看到路径寻找算法。在分析代码之后,我发现它花了60%的时间在clojure.lang.Var.getRawRoot()中。为什么这个功能消耗了这么多时间,我可以优化它吗?
(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))))
答案 0 :(得分:4)
getVarRoot发生了什么?
我真的很惊讶任何程序都花了很多时间在getRawRoot()上。所有这个方法都是从Var返回一个字段,根据clojure.lang.Var中的来源:
final public Object getRawRoot(){
return root;
}
另外,它是一个小的final方法,所以应该由任何现代JIT编译器内联......基本上任何对getRawRoot的调用都应该非常快。
我怀疑你的探查器发生了一些奇怪的事情:也许是在getRawRoot()中添加调试代码需要花费很多时间。因此,我建议在没有探查器的情况下对代码进行基准测试,并使用java -server
来查看函数的真正执行情况。
其他效果提示
确保使用 Clojure 1.3 + ,因为对var访问有一些优化,你几乎肯定会想要在这种低级代码中利用它。
如果我猜测这段代码中实际上最大的瓶颈是什么,那么我认为网格函数#(blocked? maze [%1 %2])
构造一个新的向量每次调用它来检查网格方块。如果你能重构它以便它不需要一个向量会更好,那么你可以直接使用#(blocked? maze %1 %2)
。与简单的数学运算相比,构建新集合的成本很高,因此您希望在内部循环中谨慎使用它。
您还希望确保尽可能使用原始操作,并使用(set! *unchecked-math* true)
。确保将当地人声明为原语,因此您需要例如(let [u0 (int (if x-long x0 y0)) .....] .....)
等。这样做的主要原因是避免了盒装基元的开销,这再次暗示了你想要在内部循环中避免的内存分配。