Esta función Common Lisp, que simplemente calcula cuatro vértices de los bordes de un alambre de una pared con una aritmética de nivel de jardín de infantes extremadamente simple y algunas pruebas de 'caso' parece ser responsable de asignar dinámicamente 196608 bytes por cuadro renderizado; El perfilador de SBCL me dice que esta es mi función más problemática en lo que concierne a consing. Para dar una idea general de en qué estoy trabajando, se trata de un pequeño juego de mazmorra de calabozo en primera persona, y una mazmorra tiene exactamente 32x32 celdas, y cada celda tiene 4 paredes. 32 * 32 * 4 * x = 196608, y entonces x resulta ser 48, que resulta ser 4 * 12 (4 paredes * 12 flotadores por pared? Tal vez no).¿Eliminando el "misterio-consing" en esta función Common Lisp?
Ahora, puedo mitigar fácilmente este problema de rendimiento mediante el uso de listas de visualización de OpenGL en el modo de juego, y supongo que eso es lo que seguiré haciendo. Aún así, 1) Generalmente no optimizo prematuramente, y más importante aún 2) Todavía no me gusta dejar ciertos rasguños molestos como este sin rayar, y me pregunto qué más podría haber hecho. Mi función como está, es como sigue:
(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)))))
Para un resumen de algunas de las cosas que he tratado de solucionar este problema:
Haciendo un 'declare' a 'Optimizar' para la 'velocidad' en 3 y todo lo demás en 0 (ambos en esta función, así como la única función que lo llama). Curiosamente, el generador de perfiles sí informó esta función con un poco menos de ... pero aún así consed. Estoy apuntando a cero-consing. La aritmética no debe contra.
Luego pensé 'valores' podría estar haciendo esto. Quizás, pensé, es internamente algo así como la función 'lista', que, sin duda, considera (el único propósito de la función 'lista' en el universo). ¿Qué hice para tratar de mitigar esto? Solo por el experimento, modifiqué el archivo para crear una única matriz global wall-vertex-buffer, dimensionada para caber en 12 elementos de tipo float, y modifiqué esta función para modificarla, y la función de llamada para leerla después llamando a esta función (por lo que constantemente estaría actualizando un solo conjunto de 12 flotadores guardados en el mismo lugar en la memoria, en lugar de asignar cualquier cosa). ¡Extrañamente, esto NO detuvo esta función de ser un cerdito! Entonces ... ¿estaba 'case' haciendo el consing? Me parece interesante que, mencionado anteriormente, ese número misterioso era 48. 48 = 4 * 12, tal vez esas 4 pruebas de casos multiplicadas por 12 flotantes por llamada de 'valores'. O bien, eso podría ser una coincidencia, con 48 bytes que significan algo más (ya que un flotador NO es de 1 byte, sospecho que es -es- algo más). Esto parece significativo, pero no puedo entender bien cuál debería ser mi próximo enfoque.
Intenté reemplazar 'case' con un equivalente 'cond', simplemente agarrar pajitas en este punto, tampoco hice nada.
Entonces, ¿de dónde podría venir este "misterio consing" de esta función? ¿Cómo podrían los programadores de Lisp más experimentados abordar este engañoso gremlin de un problema?
(EDIT) para @FaheemMitha, es la función que utiliza la función de calcular de pared puntos; esa función problemático fue posteriormente inline con (declamar (en línea cálculo de pared puntos)) justo antes de la definición de calcular de pared puntos:
(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)
Gracias - Yo nunca habría sospechado que los números de punto flotante, de todas las cosas, era el culpable (Cogí lo suficientemente arenques rojos para abrir un restaurante de mariscos). Ahora, sabiendo qué buscar mejor, busqué el manual de SBCL y mencioné que declamar funciones float-pesadas "en línea" como esta produciría un mejor rendimiento. Eso pareció funcionar; ahora hay cero consing en esta función, así como en la función de llamada (que, supongo, ahora están fusionados internamente en la misma función, compilación posterior). – valq
@valq: Hola. Me interesan los detalles de lo que hizo para detener la función consing. ¿Podría publicar ejemplos de su código de pre y post optimización, ya sea en la pregunta o como una respuesta separada? Gracias. –
@FaheemMitha Más o menos después de hacer un (declaim (en línea calcular puntos de la pared)), recuerdo que vi mucho menos consing en el generador de perfiles entre él y la función que lo usa (lo usó). Publicaré el código en la función que lo usó en una respuesta separada. No sé si necesitará aún más código para meterse con él, ya que también hace referencia a un par de funciones y variables globales más allá de su alcance. – valq