WP7 Problemas de rendimiento con Struct (II / II)

Realicemos un pequeño benchmark para poder evaluar cual es la pérdida de rendimiento obtenido, el cual se producirá por:

CPU: Tiempo empleado en gestionar las llamadas (realizar las copias de las estructuras)

Para el test vamos a crear un proyecto para Windows Phone 7.1 con Visual Studio, y luego lo analizaremos con el profiler de WP7 corriendo directamente en un dispositivo real.

La demo será muy simple, colocaremos un bucle en el método Draw que es donde más operaciones con struct se suelen hacer, y dentro del bucle realizaremos una llamada a un método al cual se le pasarán 50 matrices por parámetro (esto es para probar que realmente el problema viene por las copias de struct).

 

//Iteraciones será igual a 5000 para simular carga de trabajo
for (int i = 0; i < iterations; i++)
{
   //Llamada al método
}

Crearemos dos versiones del método que va dentro del bucle, en uno pasaremos las matrices por valor (lo normal) y en el otro lo haremos por referencia.

 

Por valor:

private void UpdateMatrices(Matrix world0, Matrix view0, Matrix projection0,
                                     Matrix world1, Matrix view1, Matrix projection1,
                                     Matrix world2, Matrix view2, Matrix projection2,
                                     Matrix world3, Matrix view3, Matrix projection3,
                                     Matrix world4, Matrix view4, Matrix projection4,
                                     Matrix world5, Matrix view5, Matrix projection5,
                                     Matrix world6, Matrix view6, Matrix projection6,
                                     Matrix world7, Matrix view7, Matrix projection7,
                                     Matrix world8, Matrix view8, Matrix projection8,
                                     Matrix world9, Matrix view9, Matrix projection9,
                                     Matrix world10, Matrix view10, Matrix projection10,
                                     Matrix world11, Matrix view11, Matrix projection11,
                                     Matrix world12, Matrix view12, Matrix projection12,
                                     Matrix world13, Matrix view13, Matrix projection13,
                                     Matrix world14, Matrix view14, Matrix projection14,
                                     Matrix world15, Matrix view15, Matrix projection15,
                                     Matrix world16, Matrix view16, Matrix projection16,
                                     Matrix world17, Matrix view17, Matrix projection17,
                                     Matrix world18, Matrix view18, Matrix projection18,
                                     Matrix world19, Matrix view19, Matrix projection19,
                                     Matrix world20, Matrix view20, Matrix projection20,
                                     Matrix world21, Matrix view21, Matrix projection21,
                                     Matrix world22, Matrix view22, Matrix projection22,
                                     Matrix world23, Matrix view23, Matrix projection23,
                                     Matrix world24, Matrix view24, Matrix projection24,
                                     Matrix world25, Matrix view25, Matrix projection25,
                                     Matrix world26, Matrix view26, Matrix projection26,
                                     Matrix world27, Matrix view27, Matrix projection27,
                                     Matrix world28, Matrix view28, Matrix projection28,
                                     Matrix world29, Matrix view29, Matrix projection29,
                                     Matrix world30, Matrix view30, Matrix projection30,
                                     Matrix world31, Matrix view31, Matrix projection31,
                                     Matrix world32, Matrix view32, Matrix projection32,
                                     Matrix world33, Matrix view33, Matrix projection33,
                                     Matrix world34, Matrix view34, Matrix projection34,
                                     Matrix world35, Matrix view35, Matrix projection35,
                                     Matrix world36, Matrix view36, Matrix projection36,
                                     Matrix world37, Matrix view37, Matrix projection37,
                                     Matrix world38, Matrix view38, Matrix projection38,
                                     Matrix world39, Matrix view39, Matrix projection39,
                                     Matrix world40, Matrix view40, Matrix projection40,
                                     Matrix world41, Matrix view41, Matrix projection41,
                                     Matrix world42, Matrix view42, Matrix projection42,
                                     Matrix world43, Matrix view43, Matrix projection43,
                                     Matrix world44, Matrix view44, Matrix projection44,
                                     Matrix world45, Matrix view45, Matrix projection45,
                                     Matrix world46, Matrix view46, Matrix projection46,
                                     Matrix world47, Matrix view47, Matrix projection47,
                                     Matrix world48, Matrix view48, Matrix projection48,
                                     Matrix world49, Matrix view49, Matrix projection49)
        {
                   //Dentro no hacemos nada
         }

Por referencia:

private void UpdateMatricesOptimized(ref Matrix world0, ref Matrix view0, ref  Matrix projection0,
                                             ref Matrix world1, ref Matrix view1, ref  Matrix projection1,
                                             ref Matrix world2, ref Matrix view2, ref  Matrix projection2,
                                             ref Matrix world3, ref Matrix view3, ref  Matrix projection3,
                                             ref Matrix world4, ref Matrix view4, ref  Matrix projection4,
                                             ref Matrix world5, ref Matrix view5, ref  Matrix projection5,
                                             ref Matrix world6, ref Matrix view6, ref  Matrix projection6,
                                             ref Matrix world7, ref Matrix view7, ref  Matrix projection7,
                                             ref Matrix world8, ref Matrix view8, ref  Matrix projection8,
                                             ref Matrix world9, ref Matrix view9, ref  Matrix projection9,
                                             ref Matrix world10, ref Matrix view10, ref Matrix projection10,
                                             ref Matrix world11, ref Matrix view11, ref Matrix projection11,
                                             ref Matrix world12, ref Matrix view12, ref Matrix projection12,
                                             ref Matrix world13, ref Matrix view13, ref Matrix projection13,
                                             ref Matrix world14, ref Matrix view14, ref Matrix projection14,
                                             ref Matrix world15, ref Matrix view15, ref Matrix projection15,
                                             ref Matrix world16, ref Matrix view16, ref Matrix projection16,
                                             ref Matrix world17, ref Matrix view17, ref Matrix projection17,
                                             ref Matrix world18, ref Matrix view18, ref Matrix projection18,
                                             ref Matrix world19, ref Matrix view19, ref Matrix projection19,
                                             ref Matrix world20, ref Matrix view20, ref Matrix projection20,
                                             ref Matrix world21, ref Matrix view21, ref Matrix projection21,
                                             ref Matrix world22, ref Matrix view22, ref Matrix projection22,
                                             ref Matrix world23, ref Matrix view23, ref Matrix projection23,
                                             ref Matrix world24, ref Matrix view24, ref Matrix projection24,
                                             ref Matrix world25, ref Matrix view25, ref Matrix projection25,
                                             ref Matrix world26, ref Matrix view26, ref Matrix projection26,
                                             ref Matrix world27, ref Matrix view27, ref Matrix projection27,
                                             ref Matrix world28, ref Matrix view28, ref Matrix projection28,
                                             ref Matrix world29, ref Matrix view29, ref Matrix projection29,
                                             ref Matrix world30, ref Matrix view30, ref Matrix projection30,
                                             ref Matrix world31, ref Matrix view31, ref Matrix projection31,
                                             ref Matrix world32, ref Matrix view32, ref Matrix projection32,
                                             ref Matrix world33, ref Matrix view33, ref Matrix projection33,
                                             ref Matrix world34, ref Matrix view34, ref Matrix projection34,
                                             ref Matrix world35, ref Matrix view35, ref Matrix projection35,
                                             ref Matrix world36, ref Matrix view36, ref Matrix projection36,
                                             ref Matrix world37, ref Matrix view37, ref Matrix projection37,
                                             ref Matrix world38, ref Matrix view38, ref Matrix projection38,
                                             ref Matrix world39, ref Matrix view39, ref Matrix projection39,
                                             ref Matrix world40, ref Matrix view40, ref Matrix projection40,
                                             ref Matrix world41, ref Matrix view41, ref Matrix projection41,
                                             ref Matrix world42, ref Matrix view42, ref Matrix projection42,
                                             ref Matrix world43, ref Matrix view43, ref Matrix projection43,
                                             ref Matrix world44, ref Matrix view44, ref Matrix projection44,
                                             ref Matrix world45, ref Matrix view45, ref Matrix projection45,
                                             ref Matrix world46, ref Matrix view46, ref Matrix projection46,
                                             ref Matrix world47, ref Matrix view47, ref Matrix projection47,
                                             ref Matrix world48, ref Matrix view48, ref Matrix projection48,
                                             ref Matrix world49, ref Matrix view49, ref Matrix projection49)
        {
            //No hacemos nada
        }

Resultados:

Una vez tenemos el benchmark montado, lo ejecutamos sobre un dispositivo y le conectamos el profiler, y este es el resultado:

WP_001061

 

 

WP_001062

 

 

 

benchmark

 

En la primera zona, estaba realizando las 5000 iteraciones llamando al método que hace el paso de parámetros por valor, y en la segunda etapa estaba usando el método que realiza el paso de parámetros por referencia.

 

Conclusiones

Como podemos ver, el paso de parámetros se está “comiendo” literalmente la CPU en la primera zona. Es decir, la CPU está trabajando al 100% solo en realizar las llamadas con las respectivas copias de structs, no hay tiempo de CPU para el código de nuestro juego. Sin embargo, en la segunda zona, realizando la misma operación, tenemos una CPU liberada para ejecutar nuestro código.

 

Este último dato asusta bastante, así que es importante que todo desarrollador de Windows Phone 7 lo conozca y tenga todo esto presente.

 

Proyecto de Visual Studio

WP7 Problemas de rendimiento con Struct (I / II)

Recientemente he pasado bastantes horas optimizando código para Windows Phone 7, y una de las primeras cosas que hay que tener en mente es que el CLR(Common Language Runtime) de WP7 no es el de Windows. Incluso en la última versión (Mango) en la que han hecho grandes mejoras, como por ejemplo que ahora el GC (Garbage Collector) ofrece un rendimiento más decente con 2 generaciones, o como al aprovechamiento de las instrucciones SIMD.

Otro factor a tener en cuenta para todos esos desarrolladores que se están acercando a esta plataforma para desarrollar juegos con XNA, es que a diferencia de la mayoría de APIs que existen en .NET, en XNA se aprovechan mucho las struct para aquellos tipos con tiempo de vida relativamente bajo, algo muy común en el desarrollo de videojuegos. Por ejemplo Vector3, Vector2, Color, Matrix, etc son tipos muy usados y se suelen crear y destruir estructuras de estos tipos muy rápidamente.

Si estos tipos estuviesen definidos como class, se perdería muchísimo tiempo en reservar espacio en el Heap para luego destruirlo, por este motivo se definen como struct que permite una creación y destrucción rápida cuando se almacenan en el Stack.

Podemos llegar a tener grandes problemas de rendimiento si no comprendemos bien cómo se comportan los struct en el .NET Framework. A diferencia de las instancias de class, las instancias de struct se suelen copiar cuando las pasamos como parámetros en métodos, y es importante saber detectar todos los escenarios en los que esto ocurre.

 

Métodos:

Cuando hacemos llamadas como esta:

Matrix a, b, c;
c = Math.Multiply( a, b );

Debemos saber que Matrix está definida como struct y el paso por parámetro de estas en los métodos se hace por valor (por defecto). Dentro del cuerpo del método Multiply no se tiene la referencia a las matrices a y b, si no que dentro se trabaja con una copia de ellas, es decir, se crean dos nuevas matrices y se rellenan con los 16 (4*4) valores de las matrices fuentes. Además, la matriz que devuelve el método también se devuelve como copia, si hacemos un cálculo de cuánta memoria se ha tenido que reservar para hacer esto:

4 bytes / float

16 floats / Matrix

3 copias

3 * 16 * 4 = 192 Bytes

Esto puede llegar a ser un problema, por la basura generada, la cual pueda acelerar una pasada del GC y por el coste de CPU necesario para hacer las copias.

Properties:

Las propiedades no suelen ser muy recomendables para combinarlas con las struct, ya que su set y get tienen un comportamiento parecido al de los métodos y las estructuras se van a pasar por valor (copia).

Por ejemplo:

//Matrices en propiedades
public Matrix World {get; set;}
public Matrix View {get; set;}
public Matrix Projection {get; set;}
 
//Vectores como propiedad
public Vector3 Position {get; set;}

Es común usar estas propiedades como si fuesen campos, sin tener en cuenta que cada vez que se accede a ellas internamente es como si se ejecutara un “método get” que devuelve una copia de la struct:

Vector3: 3 elementos * 4 bytes / float = 12 bytes

Matrix: 16 elementos * 4 bytes / float = 64 bytes

Este comportamiento también es algo que conociéndolo se puede aprovechar. Por ejemplo, Microsoft en su API para Matrix ha colocado un atributo estático llamada identity. De forma que cada vez que queremos inicializar una matriz con la matriz identidad hacemos:

Matrix mat1 = Matrix.Identity;
mat1.M11 = 0;
 
Matrix mat2 = Matrix.Identity;

 

Cada vez que llamamos a Matrix.Identity nos devuelve una copia de la matriz identidad almacenada dentro de la clase Matrix de forma estática. Si no fuese así y lo que nos devolviera fuese una referencia, con la segunda línea estaríamos modificando la matriz identidad y al asignársela a mat2 esta ya no representaría a la matriz identidad.

La API de XNA está llena de esto, ejemplos:

Color.Red, Color.White, Color.Black

Vector3.Up, Vector3.Zero

Operadores:

Los operadores internamente también tienen un comportamiento similar y sufrimos copias para el paso de parámetros cuando usamos struct.

Por ejemplo:

//Operador multiplicación
Matrix worldViewProj = world * view * proj;

Internamente es como si estuviésemos haciendo llamadas a un método multiplicación al cual se le pasan por parámetro las matrices de dos en dos. Esto quiere decir que se ejecutaría una llamada al método pasándole por parámetro (copia) las matrices world y view, después el resultado se devolvería también como copia, y este se volvería a pasar al método multiplicación por valor junto con la matrix proj, y el resultado se devolverá por valor también, en total:

64 bytes / Matrix * 6 copias = 384 bytes

Si añadimos que las matrices fueron definidas como propiedades tenemos que añadir una copia más por cada acceso a matrix:

3 accesos para world, view, proj, y otra copia para realizar el set y worldviewproj es propiedad

4 copias * 64 bytes / Matrix = 256 bytes

256 bytes + 384 bytes = 640 bytes

 

 

¿Qué podemos hacer para evitar todo esto?

Métodos:

Usar los métodos en los que podamos pasar los parámetros de tipo struct como referencias:

Matrix a, b, c;
 
//Paso de matrices por referencia (no se copia la estructura)
Matrix.Multiply(ref a, ref b, out c);

Propiedades:

Usar preferiblemente campos para almacenar las struct, por ejemplo las matrices World, View, Projection de la típica clase cámara, es preferible definirlas como campos públicos ya que serán consumidas desde cada objeto que se vaya a dibujar en pantalla, ahorrándonos las copias de cada acceso.

Operadores:

Sustituir los operadores por las llamadas a métodos que permiten el paso por ref de los parámetros.

//Matrix worldViewProj = world * view * projection;
 
Matrix worldViewProj, temp;
Matrix.Multiply(ref world, ref view, out temp);
Matrix.Multiply(ref temp, ref projection, out worldViewProj);

(Importante: el código aplicando estas optimizaciones suele ser menos elegante y entendible, por lo que es recomendable solo para aquellas partes “críticas” de la aplicación)