Quais otimizações podem ser feitas para código em tempo real flexível em C #?

8

Estou escrevendo um aplicativo em tempo real suave em c #. Certas tarefas, como responder a solicitações de hardware provenientes de uma rede, precisam ser concluídas dentro de uma certa quantidade de milissegundos; no entanto, não é 100% de missão crítica fazer isso (ou seja, podemos tolerar que seja pontual na maior parte do tempo, e o 1% é indesejável, mas não uma falha), daí a parte "flexível".

Agora percebo que o C # é uma linguagem gerenciada, e as linguagens gerenciadas não são particularmente adequadas para aplicativos em tempo real. No entanto, a velocidade na qual podemos realizar as tarefas em C #, bem como os recursos da linguagem, como gerenciamento de reflexão e memória, facilitam muito a tarefa de criar esse aplicativo.

Existem otimizações ou estratégias de design que podemos adotar para reduzir a quantidade de sobrecarga e aumentar o determinismo? Idealmente, eu teria os seguintes objetivos

  • Atrasar a coleta de lixo até que ela seja "segura"
  • Permitir que o coletor de lixo funcione sem interferir nos processos em tempo real
  • Prioridades de thread / processo para diferentes tarefas

Existem maneiras de fazer isso em C # e outras coisas a serem observadas em tempo real ao usar o C #?

O destino da plataforma para o aplicativo é o .NET 4.0 Client Profile no Windows 7 de 64 bits, com. Eu o configurei atualmente para o perfil do cliente, mas essa era apenas a opção padrão e não foi escolhida por nenhum motivo específico.

9a3eedi
fonte
2
Este artigo pode ajudá-lo. É destinado a desenvolvedores de videogames, mas isso também é um aplicativo em tempo real. No entanto, o artigo também é um pouco antigo e, embora eu duvide que os princípios básicos por trás de como o GC funciona tenham mudado muito, convém verificar novamente se ele ainda está atualizado.
Doval 27/08
Você menciona C #, mas não menciona sua plataforma de destino ou tempo de execução (por exemplo, .NET em um PC / Unity + Mono / Mono etc) - você pode nos dar mais alguns detalhes?
J Trana
@JTrana Eu editei com mais detalhes
9a3eedi
1) Você realmente percebeu problemas? Graças ao GC em segundo plano, seus encadeamentos devem parar brevemente apenas em determinadas partes do GC, mas muitas outras partes podem ser executadas em paralelo sem interferência. 2) De quantos milissegundos estamos falando aqui?
CodesInChaos
2
@Doval O GC da estrutura compacta usada pelo XNA em execução no xbox é muito pior do que o GC usado na estrutura normal. Houve alterações significativas no GC em .net 4.0 relacionadas à coleta de segundo plano, que devem beneficiar a latência. Com a coleção em segundo plano, a caro coleção Gen2 pode ser executada em segundo plano enquanto o aplicativo continua funcionando. Ele pode até executar coleções Gen0 / Gen1 enquanto as coleções Gen2 estão em andamento.
código é o seguinte

Respostas:

7

A otimização que atende aos seus dois primeiros marcadores é chamada de pool de objetos . Funciona por

  1. criando um pool de objetos quando o programa é iniciado,
  2. mantendo referências a esses objetos em uma lista para que eles não coletem lixo,
  3. entregar objetos ao seu programa a partir do pool, conforme necessário, e
  4. retornando objetos de volta para a piscina quando você terminar de usá-los.

Você pode encontrar um exemplo de classe que implementa um pool de objetos usando um ConcurrentBag aqui .

A prioridade do thread / processo pode ser facilmente configurada em tempo de execução. A classe Thread possui métodos e propriedades que permitem definir a prioridade e a afinidade do processador. A classe de processo contém recursos semelhantes.

Robert Harvey
fonte
3
Isso parece algo que deve ser feito depois que o aplicativo estiver completo e funcionando, como em "otimizar mais tarde". No entanto, faz sentido para mim como um método para aumentar o determinismo.
precisa saber é
1
Alguma sugestão que eu faça seria "otimizar mais cedo?"
Robert Harvey
2
O coletor de lixo possui poucas opções de configuração. É geralmente assumido que seu comportamento é ideal para a grande maioria dos programas. Observe que nem o sistema operacional Windows nem o .NET Framework devem ser usados ​​em ambientes difíceis em tempo real. Sim, eu sei que você disse suave em tempo real, mas ainda assim.
Robert Harvey
2
@ 9a3eedi: Com relação a se o pool de objetos pode ser aparafusado após o desenvolvimento do aplicativo, posso oferecer o seguinte conselho. (1) você precisará injetar uma fábrica de objetos (que funciona como o pool de objetos) em todos os objetos que você gostaria de agrupar; (2) substituir todos os métodos construtores por métodos de fábrica; (3) cada objeto possuir um método de reciclagem que retorne à fábrica de piscinas (4) implemente seus objetos para serem recicláveis: seja limpo e depois reescrito com novas informações. Você pode levar isso em consideração se precisar criar a interface da API antecipadamente.
Rwong 27/08/2014
3
Na minha experiência, os pools de objetos são úteis para reciclar matrizes grandes (como buffers). Para objetos pequenos, o tempo de execução tende a funcionar bem sem o pool manual.
CodesInChaos
3

Atrasar a coleta de lixo até que ela seja "segura"

Você pode fazer isso definindo o modo de latência do GC como LowLatency. Para mais informações, consulte esta resposta no SO .

svick
fonte
Excelente. Eu estava procurando por algo parecido com isto. Obrigado. Eu gostaria de poder marcar duas respostas corretas
9a3eedi
2

Adote a mesma abordagem que o desenvolvimento de jogos faz em um ambiente gerenciado e tente minimizar a criação / morte de objetos até o mínimo absoluto.

Por exemplo, tente criar todos os objetos que provavelmente serão necessários no início e agrupe obsessivamente, passe por ref sempre que possível, evite operações que criem objetos intermediários de curto prazo

lzcd
fonte
Alterar algumas classes para estruturas (imutáveis) ajudará a evitar a criação de objetos intermediários de curto prazo?
Aug3
A escolha entre estruturas e classes geralmente não tem um grande impacto diretamente na vida útil de um "objeto" (exceto que as estruturas vêm com alguma lógica de estilo "singleton" do tipo valor incorporada). Ser imutável pode ajudar, mas realmente depende caso a caso.
precisa saber é
1
@ 9a3eedi: usar objetos imutáveis ​​significa, na verdade, sempre que você precisa "mudar" alguma coisa, você precisa criar um novo objeto com valores modificados (e provavelmente jogar fora o objeto antigo em troca do novo). Isso é exatamente o oposto do que você deseja, pois deixará muita memória para ser limpa para o GC.
Doc Brown
@DocBrown +1. No entanto, os GCs são ajustados para objetos de vida curta. Você não paga por objetos que estão mortos quando uma coleção acontece (embora a alocação como louca aumente a frequência das coleções). Você não está errado, mas vale a pena verificar que há um problema em primeiro lugar desde reunindo quantidades para a gestão de memória manual e derrotas metade o propósito de código gerenciado (a outra metade sendo evitando comportamento indefinido.)
Doval
@DocBrown Além disso, os objetos que o compilador / tempo de execução pode provar não escapam do seu escopo, são alocados na pilha e não na pilha, portanto, em alguns casos, os objetos temporários não têm nenhum efeito sobre a coleta de lixo.
Doval
0

Lembre-se de que o ambiente de destino (Windows) é um O / S multitarefa preventivo. Em outras palavras, seu processo em tempo real será inevitavelmente antecipado em algum momento ou outro, o que introduzirá outras latências além da coleta de lixo do .NET. Você pode atenuá-lo, reduzindo o valor da fatia de tempo (quantum) (o padrão é algo como 16ms) e elevando a prioridade do seu processo (mas existem limites práticos), mas as pré-emptions / ainda devem ocorrer porque outros processos essenciais ainda precisam fazer coisas. Tudo isso não tem nada a ver com linguagem de programação; é fundamental para o sistema operacional.

Problemas de GC à parte, seu aplicativo em execução em uma máquina Windows pronta para o uso provavelmente será capaz de fornecer latências de alguns milissegundos na maior parte do tempo. Certamente não pode ser garantido 100% do tempo, ou algo próximo. Mesmo com o ajuste (quantum curto, alta prioridade), você ainda obtém altas latências ocasionais. É a natureza da besta.

Conclusão: se você precisar de tempos de resposta garantidos, especialmente na ordem de alguns milissegundos, o Windows provavelmente não é a melhor opção para o sistema operacional de entrega.

Zenilogix
fonte
Concordo. Estou disposto a aceitar alguns milissegundos de atrasos aqui e ali, daí a parte "suave em tempo real", mas, idealmente, gostaria de minimizar o máximo possível. Talvez para baixo da linha eu posso portar o aplicativo para Linux (com Mono) e talvez isso vai me permitir afinar essas coisas
9a3eedi