¿Qué optimizaciones se pueden hacer para el código de software en tiempo real en C #?

8

Estoy escribiendo una aplicación suave en tiempo real en C #. Ciertas tareas, como responder a las solicitudes de hardware provenientes de una red, deben completarse en una cierta cantidad de milisegundos; sin embargo, no es 100% crítico hacerlo (es decir, podemos tolerar que llegue a tiempo la mayor parte del tiempo, y el 1% es indeseable pero no un fracaso), de ahí la parte "blanda".

Ahora me doy cuenta de que C # es un lenguaje administrado, y los lenguajes administrados no son particularmente adecuados para aplicaciones en tiempo real. Sin embargo, la velocidad con la que podemos hacer las cosas en C #, así como las características del lenguaje, como la reflexión y la administración de memoria, hacen que la tarea de construir esta aplicación sea mucho más fácil.

¿Hay optimizaciones o estrategias de diseño que uno pueda tomar para reducir la cantidad de gastos generales y aumentar el determinismo? Idealmente tendría los siguientes objetivos

  • Retrasar la recolección de basura hasta que sea "seguro"
  • Permita que el recolector de basura funcione sin interferir con los procesos en tiempo real
  • Hilo / prioridades de proceso para diferentes tareas

¿Hay alguna forma de hacer esto en C #, y hay otras cosas a tener en cuenta con respecto al tiempo real cuando se usa C #?

El objetivo de la plataforma para la aplicación es .NET 4.0 Client Profile en Windows 7 de 64 bits, con. Lo configuré actualmente en Perfil del cliente, pero esta era solo la opción predeterminada y no se eligió por ningún motivo en particular.

9a3eedi
fuente
2
Este artículo puede ayudarte. Está dirigido a desarrolladores de videojuegos, pero también es una aplicación suave en tiempo real. Sin embargo, el artículo también es un poco antiguo, y aunque dudo que los principios básicos detrás de cómo funciona el GC hayan cambiado mucho, es posible que desee verificar que todavía esté actualizado.
Doval
Menciona C # pero no menciona su plataforma de destino o tiempo de ejecución (por ejemplo, .NET en una PC / Unity + Mono / Mono, etc.). ¿Puede darnos algunos detalles más?
J Trana
@JTrana que he editado con más detalles
9a3eedi
1) ¿Realmente notaste problemas? Gracias al GC de fondo, sus hilos solo deberían detenerse brevemente para ciertas partes del GC, pero muchas otras partes pueden ejecutarse en paralelo sin interferencia. 2) ¿De cuántos milisegundos estamos hablando aquí?
CodesInChaos
2
@Doval El GC del marco compacto utilizado por XNA que se ejecuta en el xbox es mucho peor que el GC usado en el marco normal. Hubo cambios significativos en el GC en .net 4.0 relacionados con la recopilación de antecedentes que deberían beneficiar la latencia. Con la recopilación en segundo plano, la costosa colección Gen2 puede ejecutarse en segundo plano mientras su aplicación continúa funcionando. Incluso puede ejecutar colecciones Gen0 / Gen1 mientras las colecciones Gen2 están en progreso.
CodesInChaos

Respuestas:

7

La optimización que cumple sus dos primeras viñetas se llama un conjunto de objetos . Funciona por

  1. creando un grupo de objetos cuando se inicia el programa,
  2. mantener referencias a esos objetos en una lista para que no se recolecte basura,
  3. entregar objetos a su programa desde el grupo según sea necesario, y
  4. devolver objetos a la piscina cuando haya terminado de usarlos.

Puede encontrar una clase de ejemplo que implemente un grupo de objetos usando un ConcurrentBag aquí .

La prioridad de subprocesos / procesos se puede establecer fácilmente en tiempo de ejecución. La clase Thread tiene métodos y propiedades que le permiten establecer la prioridad y la afinidad del procesador. La clase de proceso contiene instalaciones similares.

Robert Harvey
fuente
3
Esto parece algo que debería hacerse después de que la aplicación esté completa y funcionando, como en "optimizar más tarde". Sin embargo, tiene sentido para mí como un método para aumentar el determinismo.
9a3eedi
1
¿Alguna sugerencia que haga sería "optimizar antes?"
Robert Harvey
2
El recolector de basura tiene pocas opciones de configuración preciosas. En general, se supone que su comportamiento es óptimo para la gran mayoría de los programas. Tenga en cuenta que ni el sistema operativo Windows ni .NET Framework están destinados a ser utilizados en entornos difíciles en tiempo real. Sí, sé que dijiste suave en tiempo real, pero aún así.
Robert Harvey
2
@ 9a3eedi: Con respecto a si el conjunto de objetos se puede atornillar después de que se haya realizado el desarrollo de la aplicación, puedo ofrecer los siguientes consejos. (1) deberá inyectar una fábrica de objetos (que también funciona como el conjunto de objetos) en todos los objetos que desea agrupar, (2) reemplazar todos los métodos de constructor con métodos de fábrica, (3) cada objeto tiene un método de reciclaje que regrese a la fábrica de piscinas (4) implemente sus objetos para que sean reciclables: limpie y vuelva a escribir con nueva información. Puede tener esto en cuenta si necesita diseñar la interfaz API por adelantado.
rwong
3
En mi experiencia, las agrupaciones de objetos son útiles para reciclar matrices grandes (como buffers). Para objetos pequeños, el tiempo de ejecución tiende a funcionar bien sin la agrupación manual.
CodesInChaos
3

Retrasar la recolección de basura hasta que sea "seguro"

Puede hacerlo estableciendo el modo de latencia GC en LowLatency. Para obtener más información, vea esta respuesta en SO .

svick
fuente
Excelente. Estaba buscando algo como esto. Gracias. Desearía poder marcar dos respuestas correctas
9a3eedi
2

Tome el mismo enfoque que el desarrollo de juegos en un entorno administrado e intente minimizar la creación / muerte de objetos al mínimo absoluto.

Por ejemplo, intente crear todos los objetos que puedan ser necesarios al inicio y agrupar obsesivamente, pase por referencia siempre que sea posible, evite las operaciones que crean objetos intermedios a corto plazo

lzcd
fuente
¿Cambiar algunas clases a estructuras (inmutables) ayudará a evitar la creación de objetos intermedios a corto plazo?
9a3eedi
La elección entre estructuras y clases generalmente no tiene un gran impacto en la vida útil de un "objeto" directamente (excepto que las estructuras vienen con una lógica de estilo "singleton" de tipo valor incorporada). Ser inmutable puede ayudar, pero realmente depende de cada caso.
lzcd
1
@ 9a3eedi: el uso de objetos inmutables significa que, en realidad, cada vez que tiene que "mutar" algo, debe crear un nuevo objeto con valores modificados (y probablemente desechar el objeto antiguo a cambio del nuevo). Eso es exactamente lo contrario de lo que desea, ya que dejará mucha memoria para limpiar el GC.
Doc Brown
@DocBrown +1. Sin embargo, los GC están ajustados para objetos de corta duración. No paga por objetos que están muertos cuando ocurre una colección (aunque la asignación como loca aumentará la frecuencia de las colecciones). Usted no está mal, pero vale la pena comprobar que hay un problema en el primer lugar desde la puesta en común asciende a la gestión de memoria manual y derrotas medio con el propósito de código administrado (siendo la otra mitad para evitar un comportamiento indefinido.)
Doval
@DocBrown Además, los objetos que el compilador / tiempo de ejecución puede demostrar que no escapan a su alcance se asignan en la pila en lugar del montón, por lo que en algunos casos los objetos temporales no tienen ningún efecto en la recolección de basura.
Doval
0

Tenga en cuenta que el entorno de destino (Windows) es un O / S multitarea preventivo. En otras palabras, su proceso suave en tiempo real inevitablemente se adelantará en algún momento u otro, lo que introducirá otras latencias además de la recolección de basura .NET. Puede mitigarlo reduciendo el valor del intervalo de tiempo (cuántico) (el valor predeterminado es algo así como 16 ms) y elevando la prioridad de su proceso (pero hay límites prácticos), pero las anticipaciones tendrán / deben ocurrir porque otros procesos esenciales aún necesitan para hacer cosas Todo esto no tiene nada que ver con el lenguaje de programación; Es fundamental para el O / S.

Dejando a un lado los problemas de GC, su aplicación que se ejecuta en una máquina Windows lista para usar probablemente pueda entregar latencias de unos pocos milisegundos LA MAYOR PARTE DEL TIEMPO. Ciertamente no se puede garantizar el 100% del tiempo, ni nada parecido. Incluso con el ajuste (cuántica corta, alta prioridad), aún obtendrá latencias altas ocasionales. Es la naturaleza de la bestia.

En pocas palabras: si necesita tiempos de respuesta garantizados, especialmente en el orden de unos pocos milisegundos, Windows probablemente no sea la mejor opción para la entrega O / S.

Zenilogix
fuente
Estoy de acuerdo. Estoy dispuesto a aceptar unos pocos milisegundos de retrasos aquí y allá, de ahí la parte de "tiempo real suave", pero idealmente me gustaría minimizar lo más posible. Tal vez en el futuro pueda portar la aplicación a Linux (con Mono) y tal vez eso me permita ajustar estas cosas
9a3eedi