Performance 消除;“神秘思考”;在这个公共Lisp函数中?
这个常见的Lisp函数,它使用极其简单的幼儿园级算法和一些“案例”测试,简单地计算墙线框边的四个顶点,似乎负责为每个渲染帧动态分配196608字节;SBCL的分析器告诉我,这是我认为最有问题的函数。让我大致了解一下我在做什么,这是一个小的第一人称地牢爬虫游戏,一个地牢正好是32x32个牢房,每个牢房有4堵墙。32*32*4*x=196608,所以x变成48,正好是4*12(每面墙4*12个浮点数?可能不是) 现在,通过在游戏模式下使用OpenGL显示列表,我可以轻松缓解这个性能问题,我想这就是我将继续做的。尽管如此,1)我通常不会过早地优化,更重要的是2)我仍然不喜欢像这样让人讨厌的瘙痒没有抓痕,我想知道我还能做些什么。我的职能如下:Performance 消除;“神秘思考”;在这个公共Lisp函数中?,performance,common-lisp,case,Performance,Common Lisp,Case,这个常见的Lisp函数,它使用极其简单的幼儿园级算法和一些“案例”测试,简单地计算墙线框边的四个顶点,似乎负责为每个渲染帧动态分配196608字节;SBCL的分析器告诉我,这是我认为最有问题的函数。让我大致了解一下我在做什么,这是一个小的第一人称地牢爬虫游戏,一个地牢正好是32x32个牢房,每个牢房有4堵墙。32*32*4*x=196608,所以x变成48,正好是4*12(每面墙4*12个浮点数?可能不是) 现在,通过在游戏模式下使用OpenGL显示列表,我可以轻松缓解这个性能问题,我想这就是我
(defun calculate-wall-points (x y wall)
(declare (integer x y)
(keyword wall))
"Return the 4 vertices (12 floats) of a given dungeon cell wall"
(let ((xf (coerce x 'float))
(yf (coerce y 'float)))
(case wall
(:SOUTH
(values xf yf 0.0
(1+ xf) yf 0.0
(1+ xf) yf 1.0
xf yf 1.0))
(:WEST
(values xf yf 0.0
xf yf 1.0
xf (1+ yf) 1.0
xf (1+ yf) 0.0))
(:NORTH
(values xf (1+ yf) 0.0
xf (1+ yf) 1.0
(1+ xf) (1+ yf) 1.0
(1+ xf) (1+ yf) 0.0))
(:EAST
(values (1+ xf) (1+ yf) 0.0
(1+ xf) (1+ yf) 1.0
(1+ xf) yf 1.0
(1+ xf) yf 0.0))
(otherwise
(error "Not a valid heading passed for wall in function calculate-wall-points: ~A" wall)))))
总结一下我试图解决的几个问题:
(编辑)对于@FaheemMitha,是使用“计算墙点”函数的函数;该麻烦函数后来在计算墙点定义之前与(declaim(内联计算墙点))内联:
(defun render-dungeon-room (dungeon-object x y)
(declare (optimize (speed 3) (space 0) (debug 0)))
(declare (type fixnum x y))
(let ((cell (cell-at dungeon-object x y)))
(unless (null cell)
(dolist (wall-heading +basic-headings+)
(unless (eq wall-heading (opposite-heading *active-player-heading*))
(when (eql (get-wall-type cell wall-heading) :NORMAL)
(multiple-value-bind (v1x v1y v1z v2x v2y v2z v3x v3y v3z v4x v4y v4z)
(calculate-wall-points x y wall-heading)
(declare (type float v1x v1y v1z v2x v2y v2z v3x v3y v3z v4x v4y v4z))
(gl:with-primitive :quads
(if (is-edit-mode)
(case wall-heading
(:NORTH
(gl:color 0.4 0.4 0.4))
(:WEST
(gl:color 0.4 0.0 0.0))
(:SOUTH
(gl:color 0.0 0.0 0.4))
(:EAST
(gl:color 0.0 0.4 0.0)))
(gl:color 0.1 0.1 0.1))
(gl:vertex (the float v1x)
(the float v1y)
(the float v1z))
(gl:vertex (the float v2x)
(the float v2y)
(the float v2z))
(gl:vertex (the float v3x)
(the float v3y)
(the float v3z))
(gl:vertex (the float v4x)
(the float v4y)
(the float v4z)))
(gl:color 1.0 1.0 1.0)
(gl:with-primitive :line-loop
(gl:vertex (the float v1x)
(the float v1y)
(the float v1z))
(gl:vertex (the float v2x)
(the float v2y)
(the float v2z))
(gl:vertex (the float v3x)
(the float v3y)
(the float v3z))
(gl:vertex (the float v4x)
(the float v4y)
(the float v4z)))))))))
nil)消耗的内存是由分配浮点数引起的。每个函数调用返回浮点数,实际上是32位的单浮点数。考虑意味着在堆上分配了一些数据:cons单元格、数字、数组等
单浮点
是32位内存对象。4字节
(+ 1.0 2.0) -> 3.0
在上述情况下,3.0
是一个新的浮动,可能是新消耗的
现在,除了上面的计算之外还有什么呢?内部+
操作返回一个浮点3.0
。它会发生什么情况
- 它可以在处理器寄存器中返回,并用于下一个操作
- 它可以在堆栈上返回,并在那里用于下一个操作
- 在更复杂的操作中,它可能被分配到堆中,并作为指向堆值的指针返回。如果没有足够的寄存器用于所有返回值,或者堆栈帧的大小不足以用于所有返回值,则可能会出现这种情况
现在,这些浮点数以后会发生什么?它们是否以某种方式存储在列表中?存储在新数组中?存储在新CLOS对象中的新结构中
上面清楚地表明,这取决于处理器体系结构和编译器策略。x86没有多少寄存器。64位版本有更多寄存器。RISC处理器可能有更多寄存器。那么堆栈有多大,典型的堆栈帧有多大
对于涉及多个函数的更复杂的计算,优化编译器可能能够优化哪些值保留在寄存器中,从而减少计算量
上面也清楚地表明,对于普通的Lisp,没有完全通用的方法可以使浮点运算不考虑。减少考虑的能力取决于一些通用的想法和许多编译器/体系结构特定的技巧
由于您使用的是SBCL,最好在SBCL邮件列表上征求建议,并告诉他们操作系统、体系结构(intel、arm等)以及它是以32位还是64位模式运行。还需要更多的上下文代码来更好地了解如何减少考虑
一些阅读背景资料:
- 肯·安德森(几年前不幸去世,一位非常有帮助的Lisper)收集了一些关于(存档版本)的信息
编译器怎么说?如果你优化速度,它应该大声抱怨不能打开算术代码
接下来,强制发生了什么?这也是开放编码的吗
最后,请记住,您通常可以检查函数通过反汇编()生成的汇编代码。谢谢——我从来没有怀疑过浮点数是罪魁祸首(我抓到了足够多的鲱鱼来开一家海鲜餐厅)。现在我知道要找什么了,于是四处寻找
(+ (+ 1.0 2.0) 4.0) -> 7.0)