2012-09-01 12 views
8

這個Common Lisp函數簡單地用極簡單的幼兒園級算術和幾個'case'測試來計算牆壁線框邊緣的四個頂點,這似乎是每個渲染幀動態分配196608字節的原因; SBCL的分析人員告訴我,這是我的問題最重要的功能。爲了大致瞭解我的工作,它是一個小型的第一人稱地下城爬蟲遊戲,地牢正好是32x32的單元格,每個單元格有4個牆。 32 * 32 * 4 * x = 196608,所以x最終爲48,這恰好是4 * 12(4面牆* 12每面牆漂浮物?也許不是)。消除此Common Lisp函數中的「神祕感」?

現在,我可以輕鬆地通過在遊戲模式下使用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和其他所有的0(在這個函數,以及唯一的函數調用它)。奇怪的是,剖析器確實報告這個函數稍微少一點......但它仍然coninc。我的目標是零負擔。算術不應該缺點。

  2. 然後我認爲'價值觀'可能會這樣做。也許,我想,它在內部就像函數'list',毫無疑問,它是conses('list'函數在宇宙中的唯一目的)。我做了什麼努力來減輕這一點?僅僅爲了實驗的緣故,我修改了文件來製作一個單一的牆頂點緩衝區全局數組,大小適合浮點類型12個元素,並且修改了這個函數來修改它,並且調用函數在它之後讀取它調用這個函數(所以它會不斷地更新保存在內存中同一個地方的一組12個浮點數,而不是分配任何東西)。奇怪的是,這並沒有阻止這個功能成爲吝嗇鬼!那麼...是'情況'在做什麼?我覺得有趣的是,前面提到的神祕數字是48.48 = 4 * 12,也許這4個案例測試每個'價值'調用乘以12個浮點數。或者,這可能是一個巧合,48字節意味着別的東西(因爲浮點數不是1個字節,我懷疑是其他的東西)。這似乎很重要,但我不能完全理解我的下一個方法應該是什麼。

  3. 試圖用'cond'替代'case',只是在這一點上抓住吸管,也沒有做任何事情。

那麼這個函數的「神祕感」來自哪裏呢?你如何更有經驗的Lisp程序員接近這個棘手的問題?


(EDIT)for @FaheemMitha,是使用計算壁點函數的函數;麻煩的功能,後來用內聯(朗誦(在線計算牆點))之前計算壁點的定義:

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

無)

回答

9

consed內存被分配造成的彩車。每個函數調用返回浮點數,實際上是32位single-floatsConsing意味着一些數據被分配在堆上:利弊細胞,數字,陣列...

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郵件列表尋求建議,並告訴他們有關操作系統,體系結構(intel,arm,...)以及它是否在32位或64位模式下運行。更多的上下文代碼也需要提出一個更好的圖像如何得到降低。

閱讀一些背景資料:

+1

謝謝生成 - 我從來沒有懷疑浮點數,所有的東西,是罪魁禍首(我抓到足夠紅鯡魚打開海鮮餐廳)。現在知道什麼更好找,我挖掘了SBCL的手冊,並且提到聲明這樣的「內聯」浮動函數會產生更好的性能。這似乎工作;現在在這個函數中還有調用函數(我認爲這個調用函數現在被內部融合到相同的函數中,編譯後),這個調用函數現在是零。 – valq

+0

@valq:嗨。我感興趣的是你做了什麼來阻止函數的細節。您可以發佈您的優化前代碼和後期優化代碼的示例,無論是在問題中還是作爲單獨的答案?謝謝。 –

+0

@FaheemMitha在做了一個(declaim(inline calculate-wall-points))之後,我記得我看到,在它和使用(使用)它的函數之間的剖析器中,重要性低得多。我將把代碼發佈到在單獨的答案中使用它的函數中。我不知道你是否需要更多的代碼來混淆它,因爲它還引用了超出其範圍的幾個函數和全局變量。 – valq

1

編譯器說什麼?如果您針對速度進行優化,應該大聲抱怨無法打開代碼算術。

接下來,強迫發生了什麼?這是開放編碼嗎?

最後,請記住,你通常可以檢查彙編代碼的函數與拆機()