¿Cuándo son las estructuras la respuesta?


Estoy haciendo un proyecto de hobby raytracer, y originalmente estaba usando estructuras para mis objetos Vectoriales y de Rayos, y pensé que un raytracer era la situación perfecta para usarlos: creas millones de ellos, no viven más que un solo método, son livianos. Sin embargo, simplemente cambiando 'struct' a 'class' en Vector y Ray, obtuve una ganancia de rendimiento muy significativa.

¿Qué da? Ambos son pequeños (3 flotadores para Vector, 2 Vectores para un rayo), no se copian excesivamente. Los paso a métodos cuando es necesario, por supuesto, pero eso es inevitable. Entonces, ¿cuáles son las trampas comunes que matan el rendimiento cuando se usan estructuras? He leído este artículo de MSDN que dice lo siguiente:

Cuando ejecute este ejemplo, verá que el bucle struct es órdenes de magnitud más rápido. Sin embargo, es importante tener cuidado con el uso de tipos de valor cuando los trate como objetos. Esto agrega gastos adicionales de boxeo y unboxing a su programa, y puede terminar ¡te costaría más de lo que te costaría si te hubieras quedado con objetos! Para ver esto en acción, modifique el código anterior para usar una matriz de foos y barras. Usted encontrará que el rendimiento es más o menos igual.

Sin embargo, es bastante viejo (2001) y todo el "ponerlos en una matriz causa boxeo/unboxing" me pareció extraño. Es eso cierto? Sin embargo, calculé previamente los rayos primarios y los puse en una matriz, así que tomé este artículo y calculé el rayo primario cuando lo necesitaba y nunca los agregó a una matriz, pero no cambió nada: con las clases, seguía siendo 1.5 veces más rápido.

Estoy ejecutando.NET 3.5 SP1, lo que creo que solucionó un problema en el que los métodos de estructura nunca estaban alineados, por lo que tampoco puede ser.

Así que básicamente: ¿algún consejo, cosas a considerar y qué evitar?

EDITAR: Como se sugiere en algunas respuestas, he configurado un proyecto de prueba donde he intentado pasar estructuras como ref. Los métodos para agregar dos vectores:

public static VectorStruct Add(VectorStruct v1, VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static VectorStruct Add(ref VectorStruct v1, ref VectorStruct v2)
{
  return new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

public static void Add(ref VectorStruct v1, ref VectorStruct v2, out VectorStruct v3)
{
  v3 = new VectorStruct(v1.X + v2.X, v1.Y + v2.Y, v1.Z + v2.Z);
}

Por cada uno que tengo una variación del siguiente método de referencia:

VectorStruct StructTest()
{
  Stopwatch sw = new Stopwatch();
  sw.Start();
  var v2 = new VectorStruct(0, 0, 0);
  for (int i = 0; i < 100000000; i++)
  {
    var v0 = new VectorStruct(i, i, i);
    var v1 = new VectorStruct(i, i, i);
    v2 = VectorStruct.Add(ref v0, ref v1);
  }
  sw.Stop();
  Console.WriteLine(sw.Elapsed.ToString());
  return v2; // To make sure v2 doesn't get optimized away because it's unused. 
}

Todos parecen funcionar prácticamente idénticos. ¿Es posible que se optimicen por el JIT a lo que sea la forma óptima de pasar esta estructura?

EDIT2: Debo tener en cuenta que usar estructuras en mi proyecto de prueba es aproximadamente un 50% más rápido que usar una clase. Por qué esto es diferente para mi raytracer no lo sé.

Author: JulianR, 2009-02-28

12 answers

Básicamente, no los hagas demasiado grandes, y pásalos por ref cuando puedas. Descubrí esto exactamente de la misma manera... Cambiando mis clases de Vectores y Rayos a estructuras.

Con más memoria que se pasa alrededor, está obligado a causar cache thrashing.

 6
Author: TraumaPony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 01:25:09

Una matriz de estructuras sería una única estructura contigua en memoria, mientras que los elementos de una matriz de objetos (instancias de tipos de referencia) deben ser abordados individualmente por un puntero (es decir, una referencia a un objeto en el montón de basura recolectada). Por lo tanto, si aborda grandes colecciones de elementos a la vez, las estructuras le darán una ganancia de rendimiento ya que necesitan menos indirectas. Además, las estructuras no se pueden heredar, lo que podría permitir que el compilador realice optimizaciones adicionales (pero eso es solo una posibilidad y depende del compilador).

Sin embargo, las estructuras tienen una semántica de asignación bastante diferente y tampoco pueden heredarse. Por lo tanto, normalmente evitaría estructuras excepto por las razones de rendimiento dadas cuando sea necesario.


Struct

Una matriz de valores v codificados por una estructura (tipo de valor) se ve así en memoria:

Vvvv

Clase

Una matriz de valores v codificados por una clase (tipo de referencia) así:

Pppp

..v..v...v. v..

Donde p son los punteros, o referencias, que apuntan a los valores reales v en el montón. Los puntos indican otros objetos que pueden intercalarse en el montón. En el caso de los tipos de referencia necesita hacer referencia a v a través de la p correspondiente, en el caso de los tipos de valor puede obtener el valor directamente a través de su desplazamiento en la matriz.

 24
Author: ILoveFortran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 16:00:33

En las recomendaciones para cuándo usar una estructura dice que no debe ser mayor de 16 bytes. Su vector es de 12 bytes, que está cerca del límite. El Rayo tiene dos vectores, poniéndolo en 24 bytes, que está claramente por encima del límite recomendado.

Cuando una estructura tiene más de 16 bytes, ya no se puede copiar eficientemente con un solo conjunto de instrucciones, sino que se usa un bucle. Por lo tanto, al pasar este límite "mágico", en realidad estás haciendo mucho más trabajo cuando pasas un struct que cuando se pasa una referencia a un objeto. Esta es la razón por la que el código es más rápido con las clases a pesar de que hay más sobrecarga al asignar los objetos.

El Vector todavía podría ser una estructura, pero el rayo es simplemente demasiado grande para funcionar bien como una estructura.

 10
Author: Guffa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 04:04:24

Cualquier cosa escrita con respecto al boxeo/unboxing antes de los genéricos.NET se puede tomar con algo de un grano de sal. Los tipos de colección genéricos han eliminado la necesidad de encajonar y desbarbar los tipos de valor, lo que hace que el uso de estructuras en estas situaciones sea más valioso.

En cuanto a su desaceleración específica, probablemente necesitaríamos ver algún código.

 9
Author: Erik Forbes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 01:12:22

Creo que la clave está en estas dos declaraciones de su post:

Se crean millones de ellos

Y

Los paso a los métodos cuando es necesario, por supuesto

Ahora, a menos que su estructura sea menor o igual a 4 bytes de tamaño (u 8 bytes si está en un sistema de 64 bits), está copiando mucho más en cada llamada a método que si simplemente pasó una referencia de objeto.

 6
Author: Andrew Hare,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 01:17:54

Lo primero que buscaría es asegurarme de que has implementado explícitamente Equals y GetHashCode. No hacer esto significa que la implementación en tiempo de ejecución de cada uno de estos hace algunas operaciones muy costosas para comparar dos instancias de estructura (internamente utiliza la reflexión para determinar cada uno de los campos privados y luego los comprueba para la igualdad, esto causa una cantidad significativa de asignación).

En general, sin embargo, lo mejor que puede hacer es ejecutar su código bajo una perfilador y ver dónde están las partes lentas. Puede ser una experiencia reveladora.

 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 03:12:03

¿Ha perfilado la solicitud? El perfilado es la única manera segura de ver dónde está el problema de rendimiento real. Hay operaciones que generalmente son mejores / peores en estructuras, pero a menos que perfiles, solo estarás adivinando cuál es el problema.

 4
Author: JaredPar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 01:40:30

Aunque la funcionalidad es similar, las estructuras suelen ser más eficientes que las clases. Debe definir una estructura, en lugar de una clase, si el tipo funcionará mejor como un tipo de valor que como un tipo de referencia.

Específicamente, los tipos de estructura deben cumplir todos estos criterios:

  • Representa lógicamente un único valor
  • Tiene un tamaño de instancia inferior a 16 bytes
  • No se cambiará después de la creación
  • No se enviará a una referencia tipo
 2
Author: Gineer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-17 15:00:26

Utilizo estructuras básicamente para objetos de parámetro, devolviendo varias piezas de información de una función, y... nada más. No sé si es "correcto" o "incorrecto", pero eso es lo que hago.

 0
Author: Instance Hunter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 06:05:38

Mi propio trazador de rayos también usa vectores de estructura (aunque no rayos) y cambiar el Vector a clase no parece tener ningún impacto en el rendimiento. Actualmente estoy usando tres dobles para el vector por lo que podría ser más grande de lo que debería ser. Una cosa a tener en cuenta, y esto podría ser obvio, pero no era para mí, y eso es ejecutar el programa fuera de visual studio. Incluso si lo configura como versión optimizada, puede obtener un aumento de velocidad masivo si inicia el exe fuera de VS. Any benchmarking usted debe tomar esto en consideración.

 0
Author: Morten Christiansen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-17 10:13:03

Si las estructuras son pequeñas, y no existen demasiadas a la vez, debe colocarlas en la pila (siempre y cuando sea una variable local y no un miembro de una clase) y no en el montón, esto significa que la GC no necesita ser invocada y la asignación/desasignación de memoria debe ser casi instantánea.

Cuando se pasa una estructura como parámetro a la función, la estructura se copia, lo que no solo significa más asignaciones/desasignaciones (de la pila, que es casi instantánea, pero aún tiene sobrecarga), pero la sobrecarga en solo la transferencia de datos entre las 2 copias. Si pasa a través de referencia, esto no es un problema, ya que solo le está diciendo de dónde leer los datos, en lugar de copiarlos.

No estoy 100% seguro de esto, pero sospecho que devolver matrices a través de un parámetro 'out' también puede darle un aumento de velocidad, ya que la memoria en la pila está reservada para ella y no necesita ser copiada ya que la pila se "desenrolla" al final de las llamadas a la función.

 -1
Author: Grant Peters,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-01 06:19:31

También puede convertir estructuras en objetos nullables. Las clases personalizadas no podrán ser creadas

Como

Nullable<MyCustomClass> xxx = new Nullable<MyCustomClass>

Donde con una estructura es nullable

Nullable<MyCustomStruct> xxx = new Nullable<MyCustomStruct>

Pero usted estará (obviamente) perdiendo todas sus características de herencia

 -5
Author: BozoJoe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-02-28 01:26:43