Pregunta

Actualmente estoy trabajando en un trazador de rayos en C# como proyecto de hobby.Estoy tratando de lograr una velocidad de renderizado decente implementando algunos trucos de una implementación de C++ y me he encontrado con un problema.

Los objetos de las escenas que representa el trazador de rayos se almacenan en una estructura KdTree y los nodos del árbol, a su vez, se almacenan en una matriz.La optimización con la que tengo problemas es al intentar encajar tantos nodos de árbol como sea posible en una línea de caché.Una forma de hacer esto es que los nodos contengan un puntero al nodo secundario izquierdo únicamente.Entonces está implícito que el hijo derecho sigue directamente al hijo izquierdo en la matriz.

Los nodos son estructuras y, durante la construcción del árbol, una clase de administrador de memoria estática los coloca con éxito en la matriz.Cuando empiezo a atravesar el árbol, al principio parece funcionar bien.Luego, en un punto inicial de la representación (aproximadamente en el mismo lugar cada vez), el puntero secundario izquierdo del nodo raíz de repente apunta a un puntero nulo.Llegué a la conclusión de que el recolector de basura ha movido las estructuras mientras la matriz se encuentra en el montón.

Intenté varias cosas para fijar las direcciones en la memoria, pero ninguna parece durar toda la vida útil de la aplicación, como necesito.La palabra clave 'fija' solo parece ayudar durante las llamadas a métodos únicos y la declaración de matrices 'fijas' solo se puede hacer en tipos simples que no lo son un nodo.¿Existe una buena manera de hacer esto o estoy demasiado avanzado en el camino de cosas para las que C# no estaba diseñado?

Por cierto, cambiar a C++, aunque quizás sea la mejor opción para un programa de alto rendimiento, no es una opción.

¿Fue útil?

Solución

En primer lugar, si usa C# normalmente, no puede obtener repentinamente una referencia nula debido a que el recolector de basura mueve cosas, porque el recolector de basura también actualiza todas las referencias, por lo que no necesita preocuparse de que mueva cosas.

Puedes fijar cosas en la memoria, pero esto puede causar más problemas de los que resuelve.Por un lado, impide que el recolector de basura compacte la memoria correctamente y puede afectar el rendimiento de esa manera.

Una cosa que diría de tu publicación es que el uso de estructuras puede no ayudar al rendimiento como esperas.C# no logra alinear ninguna llamada a método que involucre estructuras y, aunque solucionaron este problema en su última versión beta en tiempo de ejecución, las estructuras con frecuencia no funcionan tan bien.

Personalmente, diría que trucos de C++ como este generalmente no tienden a trasladarse demasiado bien a C#.Quizás tengas que aprender a dejarte llevar un poco;Puede haber otras formas más sutiles de mejorar el rendimiento;)

Otros consejos

¿Qué está haciendo realmente su administrador de memoria estática?A menos que esté haciendo algo inseguro (P/Invoke, código inseguro), el comportamiento que está viendo es un error en su programa y no se debe al comportamiento del CLR.

En segundo lugar, ¿qué quiere decir con "indicador" con respecto a los vínculos entre estructuras?¿Se refiere literalmente a un puntero KdTree* inseguro?No hagas eso.En su lugar, utilice un índice en la matriz.Como espero que todos los nodos de un solo árbol se almacenen en la misma matriz, no necesitará una referencia separada a la matriz.Un solo índice será suficiente.

Finalmente, si realmente debe usar punteros KdTree*, entonces su administrador de memoria estática debería asignar un bloque grande usando, por ejemplo.Marshal.AllocHGlobal u otra fuente de memoria no administrada;ambos deberían tratar este bloque grande como una matriz KdTree (es decir,indexar un estilo KdTree* C) y debería subasignar nodos de esta matriz, colocando un puntero "libre".

Si alguna vez tiene que cambiar el tamaño de esta matriz, necesitará actualizar todos los punteros, por supuesto.

La lección básica aquí es que los punteros inseguros y la memoria administrada no no mezclar fuera de los bloques 'fijos', que por supuesto tienen afinidad con el marco de pila (es decir,cuando la función regresa, el comportamiento anclado desaparece).Hay una manera de fijar objetos arbitrarios, como su matriz, usando GCHandle.Alloc(yourArray, GCHandleType.Pinned), pero es casi seguro que no querrá seguir esa ruta.

Obtendrá respuestas más sensatas si describe con más detalle lo que está haciendo.

Si usted en realidad Si desea hacer esto, puede usar el método GCHandle.Alloc para especificar que se debe fijar un puntero sin liberarlo automáticamente al final del alcance como la declaración fija.

Pero, como han dicho otras personas, hacer esto supone ejercer una presión indebida sobre el recolector de basura.¿Qué tal simplemente crear una estructura que contenga un par de nodos y luego administrar una matriz de NodePairs en lugar de una matriz de nodos?

Si realmente desea tener acceso completamente no administrado a una parte de la memoria, probablemente sería mejor asignar la memoria directamente desde el montón no administrado en lugar de fijar permanentemente una parte del montón administrado (esto evita que el montón pueda funcionar correctamente). compacto en sí).Una forma rápida y sencilla de hacerlo sería utilizar el método Marshal.AllocHGlobal.

¿Es realmente prohibitivo almacenar el par de referencia e índice de matriz?

¿Qué está haciendo realmente su administrador de memoria estática?A menos que esté haciendo algo inseguro (P/Invoke, código inseguro), el comportamiento que está viendo es un error en su programa y no se debe al comportamiento del CLR.

De hecho, estaba hablando de indicaciones inseguras.Lo que quería era algo como Marshal.AllocHGlobal, aunque con una vida útil superior a una única llamada a método.Pensándolo bien, parece que simplemente usar un índice es la solución correcta, ya que podría haberme quedado demasiado atrapado en imitar el código C++.

Una cosa que diría de tu publicación es que el uso de estructuras puede no ayudar al rendimiento como esperas.C# no logra alinear ninguna llamada a método que involucre estructuras y, aunque solucionaron este problema en su última versión beta en tiempo de ejecución, las estructuras con frecuencia no funcionan tan bien.

Investigué esto un poco y veo que se ha solucionado en .NET 3.5SP1;Supongo que a eso te referías como versión beta en tiempo de ejecución.De hecho, ahora entiendo que este cambio representó una duplicación de mi velocidad de renderizado.Ahora, las estructuras están agresivamente alineadas, lo que mejora enormemente su rendimiento en los sistemas X86 (X64 tenía un mejor rendimiento de estructura de antemano).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top