在这个Common Lisp函数中消除“神秘消耗”?

时间:2012-09-01 17:07:23

标签: performance common-lisp case

这个Common Lisp函数简单地计算了墙的线框边缘的四个顶点,使用极其简单的幼儿园级算术和一些“大小写”测试,似乎负责为每个渲染帧动态分配196608个字节; SBCL的分析器告诉我,就业务而言,这是我最有问题的功能。为了大致了解我正在做什么,它是一个小型的第一人称地下城爬虫游戏,地牢正好是32x32个细胞,每个细胞有4个墙。 32 * 32 * 4 * x = 196608,所以x竟然是48,这恰好是4 * 12(每面墙4个墙* 12个浮点数?也许不是)。

现在,我可以通过在游戏模式下使用OpenGL显示列表轻松缓解此性能问题,我想这就是我将继续做的事情。尽管如此,1)我通常不会过早地进行优化,更重要的是2)我仍然不喜欢留下像这样没有划痕的令人烦恼的痒,我想知道我还能做些什么。我的功能如下:

(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)))))

总结一下我试图解决的一些问题:

  1. 在3处为'speed'执行'declare'以'优化',在0处执行其他所有操作(在此函数中,以及调用它的唯一函数)。奇怪的是,剖析器确实报告了这个功能稍微减少......但它仍然存在。我的目标是零消费。算术不应该缺点。

  2. 然后我认为'价值'可能会这样做。也许,我认为,它内部就像功能'列表',毫无疑问,这是一个('列表'功能在宇宙中的唯一目的)。我做了什么来试图缓解这个?仅仅为了实验,我修改了文件以制作一个 wall-vertex-buffer 全局数组,其大小适合float类型的12个元素,并修改了这个函数来修改它,以及调用函数在调用此函数后从中读取(因此它将不断更新保存在内存中相同位置的一组12个浮点数,而不是分配任何内容)。奇怪的是,这并没有阻止这个功能成为一个小猪!所以......做案的'案例'?我觉得有趣的是,前面提到过,神秘数字是48. 48 = 4 * 12,也许这4个案例测试每次'值'调用12次浮点数。或者,这可能是巧合,48字节意味着其他东西(因为浮点数不是1字节,我怀疑是 - 是 - 其他的东西)。这看起来很重要,但我不能完全理解我的下一步应该是什么。

  3. 尝试用'cond'替代'替换'case',此时只是抓住吸管,也没有做任何事情。

  4. 那么这个功能的“神秘消息”来自哪里呢?您如何更有经验的Lisp程序员接近这个棘手的问题?


    @FaheemMitha的

    (编辑),是使用calculate-wall-points函数的函数;这个麻烦的函数后来在calculate-wall-points的定义之前用(declaim(inline calculate-wall-points))内联:

    (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)))))))))
    

    无)

2 个答案:

答案 0 :(得分:9)

consed 内存是由分配浮点数引起的。每个函数调用都返回浮点数,实际上是32位single-floats Consing 意味着在堆上分配了一些数据:cons单元格,数字,数组......

single-float是32位内存对象。 4个字节。

(+ 1.0 2.0)  ->  3.0

在上面的情况下,3.0是一个新的浮点数,可能是新的 consed

(+ (+ 1.0 2.0) 4.0)  -> 7.0)

现在上面的计算是什么?内部+操作返回浮点3.0。会发生什么事?

  • 它可以在处理器寄存器中返回并在那里用于下一步操作。
  • 它可以在堆栈上返回并在那里用于下一个操作
  • 在更复杂的操作中,它可以在堆中分配并作为指向堆值的指针返回。如果没有足够的寄存器用于所有返回值,或者堆栈帧的大小不足以容纳所有返回值,则可能就是这种情况。

现在这些花车会发生什么?他们以某种方式存储?在列表中?在一个新阵列?在新structure?在新的CLOS对象中?

上面清楚地表明它取决于处理器架构和编译器策略。 x86的寄存器不多。 64位版本有更多。 RISC处理器可能具有更多寄存器。那么堆栈有多大,典型的堆栈帧有多大?

对于涉及多个函数的更复杂的计算,优化编译器可能能够优化哪些值保留在寄存器中,从而减少了消耗。

上面还清楚地表明,对于Common Lisp,没有完全通用的配方如何使浮点运算不存在。减少使用的能力取决于一些一般性的想法和许多特定的编译器/体系结构技巧

由于您使用的是SBCL,最好在SBCL邮件列表上寻求建议,并告诉他们操作系统,架构(英特尔,手臂......)以及它是否在32位或64位模式下运行。还需要更多的上下文代码来更好地了解如何减少消耗。

阅读的一些背景信息:

答案 1 :(得分:1)

编译器说什么?如果你优化速度,它应该大声抱怨无法打开代码算法。

接下来,胁迫会发生什么?这也是开放编码的吗?

最后,请记住,您通常可以使用disassemble()

检查函数生成的汇编代码