¿Cuáles son las dificultades típicas al escribir juegos con un lenguaje administrado como C #? [cerrado]

66

¿Qué inconvenientes encontraste al escribir juegos para PC con un lenguaje administrado como C # y cómo los resolviste?

Michael Klement
fuente
Esta pregunta se hace mejor en Stack Overflow, ya que hay poco sobre eso que sea específico de los juegos.
Chris Garrett
31
@Chris: Muy en desacuerdo: ¡la pregunta menciona específicamente los juegos! Los problemas que encuentra cuando tiene que enviar una actualización cada 16 ms son muy diferentes a los que obtendría en la mayoría de las aplicaciones de escritorio.
Andrew Russell
La pregunta no está clara. Java y C # difieren lo suficiente para que solo los consejos muy generales sean aplicables a ambos. Todas las respuestas hasta ahora han sido C #. Tampoco se menciona la plataforma de destino: los buenos consejos pueden variar según el dispositivo (p. Ej., Programación para un teléfono móvil, zune, xbox, diferente de la programación para una PC). Incluso ha sido una pregunta lo suficientemente amplia como para que alguien haya respondido que los lenguajes administrados en sí mismos son la "trampa".
paulecoyote
@paulecoyote: cambié a pregunta para preguntar solo sobre C #. Además, como no se menciona una plataforma específica, se trata de la PC.
Michael Klement
@Michael suponiendo que la plataforma es una suposición peligrosa ya que difieren mucho en la implementación, sería bueno mencionar Windows específicamente y soltar "me gusta" y "Java" por completo.
paulecoyote

Respuestas:

69

No sé mucho sobre Java, así que esto es desde el punto de vista de un desarrollador .net.

El más grande con diferencia es la basura. El recolector de basura .NET en Windows hace un trabajo fantástico, y puedes escaparte sin cuidarlo en su mayor parte. En Xbox / Windows Phone 7 eso es diferente. Si obtiene puestos cada pocos cuadros, la recolección de basura puede estar causándole problemas. Por el momento, se dispara después de cada asignación de 1 MB.

Aquí hay algunos consejos para lidiar con la basura. No debería tener que preocuparse por la mayoría de estos en realidad, pero pueden ser útiles algún día.

  • Dibuja el contenido de GC.GetTotalMemory()a la pantalla. Esto te da una aproximación de la cantidad de bytes asignados que usa tu juego. Si apenas se mueve, lo estás haciendo bien. Si está subiendo rápido, tienes problemas.
  • Intenta asignar todos tus objetos de montón por adelantado. Si no asignas todo antes de que comience el juego, cada vez que alcances una mega de asignaciones, te detendrás. Sin asignaciones, sin colecciones. Tan sencillo como eso.
  • Después de cargar, llame GC.Collect(). Si sabe que la mayoría de sus grandes asignaciones están fuera del camino, solo es bueno informarle al sistema.
  • NO LLAME a GC.Collect()cada cuadro. Puede parecer una buena idea mantenerse al tanto de su basura y todo eso, pero recuerde que lo único peor que una recolección de basura es la recolección de basura.
  • Busque de dónde viene su basura. Hay algunas causas comunes como la concatenación de cadenas en lugar de usarlas StringBuilder(¡cuidado, StringBuilderno es una bala mágica y aún puede causar asignaciones! Esto significa que operaciones simples como agregar un número al final de una cadena pueden crear cantidades sorprendentes de basura) o El uso de foreachbucles sobre colecciones que usan la IEnumerableinterfaz también puede crear basura sin que usted lo sepa (por ejemplo, foreach (EffectPass pass in effect.CurrentTechnique.Passes)es común)
  • Utilice herramientas como el generador de perfiles de memoria CLR para averiguar dónde se asigna la memoria. Hay muchos tutoriales sobre cómo usar esta herramienta.
  • Cuando sepa dónde está asignando durante el juego, vea si puede usar trucos como agrupar objetos para reducir ese recuento de asignación.
  • Si todo lo demás falla, ¡haga que sus colecciones se ejecuten más rápido! El GC en el marco compacto sigue cada referencia en su código para descubrir qué objetos ya no están en uso. ¡Refactorice su código y use menos referencias!
  • Recuerde usar IDisposableen clases que contengan recursos no administrados. Puede usarlos para limpiar la memoria que el GC no puede liberar.

La otra cosa a tener en cuenta es el rendimiento de coma flotante. Si bien .NET JITer realiza una buena cantidad de optimizaciones específicas del procesador, no puede usar SSE ni ningún otro conjunto de instrucciones SIMD para acelerar sus cálculos de coma flotante. Esto puede causar una gran diferencia de velocidad entre C ++ y C # para los juegos. Si usa mono, tienen algunas bibliotecas especiales de matemáticas SIMD que puede aprovechar.

Cubed2D
fuente
De acuerdo completamente. Las implementaciones del recolector de basura en las plataformas "menores" parecen ser basura completa.
Krisc
Buena publicación, sin embargo, con respecto a su declaración sobre "asignar objetos de montón por adelantado", recomiendo leer La verdad sobre los tipos de valor
Justin
Gran punto allí, cuando optimizas el código, realmente vale la pena entender la plataforma para la que estás tratando de optimizar. Eso no fue realmente un comentario sobre los tipos de valor / tipos de referencia, es probable que cualquier objeto de larga vida no esté en la pila. Realmente se trata de asegurarse de que realice la mayor cantidad posible de sus asignaciones en el momento de la carga, no golpee esa barrera mágica de 1mb durante el juego. Todas las implementaciones de .net que xna apunta también tienen una garantía clara, los objetos asignados muy juntos en el tiempo estarán cerca en el espacio, lo que puede ser bueno para el rendimiento.
Cubed2D
También parece que olvidé mencionar que el marco compacto actual en el xbox es alérgico a las llamadas a métodos en línea. Sin embargo, conserve ese para sus peores escenarios de rendimiento, ¡usar las versiones de referencia de los métodos matemáticos parece lo suficientemente feo como es!
Cubed2D
10

Un error de rendimiento típico es no considerar al recolector de basura en el diseño / desarrollo del juego. Producir demasiada basura puede conducir a "hipo" en el juego, lo que ocurre cuando el GC funciona durante un tiempo considerable.

Para C #, el uso de objetos de valor y la declaración "using" pueden aliviar la presión del GC.

Matias Valdenegro
fuente
3
Además, puede indicarle al recolector de basura que se ejecute explícitamente si acaba de finalizar un bucle pesado de asignación / libre o si descubre que tiene ciclos de reserva.
BarrettJ
16
¡La usingdeclaración no tiene nada que ver con la recolección de basura! Es para IDisposableobjetos, que son para liberar recursos no administrados (es decir, aquellos que no son manejados por el recolector de basura ).
Andrew Russell
9

Diría que los mayores problemas que he encontrado al escribir juegos en C # ha sido la falta de bibliotecas decentes. La mayoría de los que he encontrado son puertos directos, pero incompletos, o contenedores sobre una biblioteca de C ++ que incurren en una penalización de alto rendimiento por la clasificación. (Estoy hablando específicamente sobre MOgre y Axiom para la biblioteca OGRE, y BulletSharp para la biblioteca de física Bullet)

Los lenguajes administrados (a diferencia de Interpretado, ya que Java o C # ya no se interpretan realmente) pueden ser tan rápidos como los lenguajes nativos si tienes un buen conocimiento de lo que realmente los hace lentos (clasificación, recolección de basura). Creo que el verdadero problema es que los desarrolladores de bibliotecas aún no se han dado cuenta de eso.

Karantza
fuente
Ese es un buen punto sobre C # y Java siendo administrado en lugar de interpretado ... edité mi pregunta para hacerlo más preciso :)
Michael Klement
2
La clasificación general es un cuello de botella en el rendimiento, pero también ayuda a tener en cuenta los tipos que se pueden borrar, tipos que se pueden asignar directamente a la memoria no administrada sin un impacto significativo en el rendimiento. msdn.microsoft.com/en-us/library/75dwhxf7.aspx
Sean Edwards el
8

Como otros han dicho, las pausas de recolección de GC son el mayor problema. El uso de agrupaciones de objetos es una solución típica.

munificente
fuente
4

C # y Java no se interpretan. Se compilan en un bytecode intermedio que, después de JIT , se vuelve tan rápido como el código nativo (o lo suficientemente cerca como para ser insignificante)

El mayor obstáculo que he encontrado es liberar recursos que afectan directamente la experiencia del usuario. Estos lenguajes no admiten automáticamente la finalización determinista como lo hace C ++, lo que, si no espera, puede dar lugar a cosas como mallas flotando sobre la escena después de pensar que fueron destruidas. (C # logra la finalización determinista a través de IDisposable , no estoy seguro de lo que hace Java).

Aparte de eso, los lenguajes administrados son realmente mucho más capaces de manejar el tipo de rendimiento que requieren los juegos de lo que obtienen crédito. El código administrado bien escrito es mucho más rápido que el código nativo mal escrito.

Sean Edwards
fuente
Sí, gracias por la nota, ya corrigió lo interpretado / administrado;) También, buen punto con las mallas flotando sobre la escena. No pensé en eso cuando pensé en los problemas de GC ...
Michael Klement
1
IDisposablepermite la limpieza determinista de recursos críticos y no administrados, pero no afecta directamente la finalización o el recolector de basura.
Sam Harwell
4

Ni Java ni C # se interpretan. Ambos se compilan en código máquina nativo.

El mayor problema con ambos y con los juegos es tener que codificar de tal manera que nunca se acumulen basura durante el juego. El número de aros por los que tienes que saltar para lograr eso casi supera los beneficios de usarlos en primer lugar. La mayoría de las funciones que hacen que esos lenguajes sean divertidos para la aplicación o la programación del servidor deben evitarse para la programación del juego; de lo contrario, obtendrá largas pausas durante el juego a medida que se disparan y se recolecta basura.

gman
fuente
La recolección de basura se puede tratar, al menos en C #, razonablemente bien, por lo que no diría que es un factor decisivo. Con un correcto subproceso y conocimiento del estado del programa, puede evitar problemas de rendimiento. Todavía es otra cosa en la que debe pensar, y hace que el lenguaje administrado sea un poco menos administrado.
Karantza
44
Vale la pena señalar que en .NET 4, el recolector de basura admite la recolección en segundo plano para las tres generaciones de objetos. Esto debería minimizar efectivamente el impacto en el rendimiento de la recolección de basura en los juegos. Enlace relevante: geekswithblogs.net/sdorman/archive/2008/11/07/…
Mike Strobel
Los recolectores de basura están evolucionando, y como señaló Mike Strobel, algunos ya están en producción que casi eliminan este escollo.
Sam Harwell
2
Ni C # ni Java se compilan en código máquina nativo. C # compila a MSIL y Java para bytecode. La recolección de basura no dará pausas "largas", pero puede dar "hipo".
Cloudanger
2

Un gran obstáculo que veo al hacer juegos con lenguajes como estos (o al usar herramientas como XNA, TorqueX engine, etc.) es que será difícil encontrar un equipo de buenas personas con la experiencia necesaria para crear un juego equivalente a lo que sería bastante fácil encontrar personas en C ++ y OpenGL / DirectX.

La industria de desarrollo de juegos todavía está muy inmersa en C ++, ya que la mayoría de las herramientas y canalizaciones que se utilizan para impulsar juegos pequeños grandes o bien pulidos se escribieron en C ++, y hasta donde yo sé TODOS los kits de desarrollo oficiales que pueda get para XBox, PS3 y Wii se lanzan solo con compatibilidad para C ++ (el conjunto de herramientas XBox puede administrarse más hoy en día, ¿alguien sabe más?)

Si quieres desarrollar juegos para consolas en este momento, obtienes XNA y C # en XBox y luego solo en una parte lateral de la biblioteca de juegos llamada XBox Live Indie Games. Algunos de los que ganan concursos, etc., son elegidos para portar su juego al XBox Live Arcade real. Aparte de eso, planea hacer un juego para PC.

mikeschuld
fuente