7

このCommonLisp関数は、非常に単純な幼稚園レベルの演算で壁のワイヤーフレームエッジの4つの頂点を単純に計算し、いくつかの「ケース」テストがレンダリングされたフレームごとに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. 'declare'を実行して'speed'を3に、その他すべてを0に(この関数とそれを呼び出す唯一の関数の両方で)最適化します。不思議なことに、プロファイラーはこの関数の結果をわずかに少なく報告しました...しかしそれでも問題はありません。ゼロコンシングを目指しています。算術は短所であってはなりません。

  2. それから私は「値」がこれをしているのではないかと思いました。おそらく、それは内部的には関数'list'のようなものであり、間違いなくそれを意味します('list'関数は宇宙での唯一の目的です)。これを軽減するために私は何をしましたか?実験のために、ファイルを変更して単一のwall-vertex-bufferを作成しましたグローバル配列、float型の12個の要素に適合するサイズで、この関数を変更して変更し、呼び出し元の関数を変更して、この関数を呼び出した後にそれから読み取ります(したがって、同じに保持されている12個のfloatの1つのセットを常に更新します何かを割り当てる代わりに、メモリに配置します)。不思議なことに、これはこの関数がおしゃべりになるのを止めませんでした!それで...「ケース」はconsingをしていましたか?前述のように、ミステリー番号が48であったことは興味深いと思います。48= 4 * 12、おそらくこれらの4つのケーステストに「値」呼び出しごとに12フロートを掛けたものです。または、それは偶然の一致である可能性があり、48バイトは他の何かを意味します(floatは1バイトではないので、私は-is-何か他のものだと思います)。これは重要なことのように思えますが、次のアプローチがどうあるべきかについて頭を悩ませることはできません。

  3. 'case'を'cond'に置き換えようとしましたが、この時点でストローをつかんでも何もしませんでした。

では、この関数の「ミステリー・コンシング」はどこから来ているのでしょうか?より経験豊富なLispプログラマーは、このトリッキーな問題のグレムリンにどのようにアプローチしますか?


(EDIT)@FaheemMithaの場合、calculate-wall-points関数を使用した関数です。その厄介な関数は、calculate-wall-pointsの定義の直前に(declaim(inlinecalculate-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)))))))))

nil)

4

2 に答える 2

8

consedメモリは、フロートを割り当てることによって発生します各関数呼び出しは、実際には32ビットのfloatを返しますsingle-floatsConsingは、いくつかのデータがヒープに割り当てられることを意味します:consセル、数値、配列、...

Asingle-floatは32ビットメモリオブジェクトです。4バイト。

(+ 1.0 2.0)  ->  3.0

上記の場合3.0、新しいフロートであり、おそらく新しくconsedされます。

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

さて、上記の計算の上に何がありますか?内部+操作はfloatを返します3.0。どうなりますか?

  • プロセッサレジスタに戻され、そこで次の操作に使用される場合があります。
  • スタックに戻され、次の操作に使用される場合があります
  • より複雑な操作では、ヒープに割り当てられ、ヒープ値へのポインターとして返される場合があります。これは、すべての戻り値に対して十分なレジスタがない場合、またはスタックフレームのサイズがすべての戻り値に対して十分に大きくない場合に当てはまります。

では、後でこれらのフロートはどうなりますか?どういうわけか保管されていますか?リストに?新しいアレイでは?新しいstructure?新しいCLOSオブジェクトでは?

上記は、それがプロセッサアーキテクチャとコンパイラ戦略に依存することを明らかにしています。x86には多くのレジスタがありません。64ビットバージョンにはさらに多くのものがあります。RISCプロセッサには、さらに多くのレジスタが含まれる場合があります。では、スタックの大きさと一般的なスタックフレームの大きさはどれくらいですか?

いくつかの関数を含むより複雑な計算の場合、最適化コンパイラーは、レジスターにとどまる値を最適化して、結果を減らすことができる場合があります。

上記はまた、Common Lispの場合、float操作を非consingにする方法の完全に一般的なレシピがないことを明確にしています。consingを減らす能力は、いくつかの一般的なアイデアと多くのコンパイラ/アーキテクチャ固有のトリックに依存します。

SBCLを使用しているので、SBCLメーリングリストでアドバイスを求め、OS、アーキテクチャ(Intel、Armなど)、および32ビットモードと64ビットモードのどちらで実行されているかについても説明することをお勧めします。consingを減らす方法をよりよく理解するには、より多くのコンテキストコードも必要です。

読むためのいくつかの背景情報:

于 2012-09-01T18:01:42.870 に答える
0

コンパイラは何と言っていますか?速度を最適化すると、算術演算をオープンコードできないことについて大声で文句を言うはずです。

次に、強制で何が起こっているのですか?これもオープンコーディングされていますか?

最後に、通常、関数がdisassemble()で生成するアセンブリコードを検査できることを忘れないでください。

于 2012-09-01T18:04:04.570 に答える