Quais são as armadilhas típicas ao escrever jogos com um idioma gerenciado como o C #? [fechadas]

66

Que armadilhas você encontrou ao escrever jogos para o PC com uma linguagem gerenciada como C # e como os resolveu?

Michael Klement
fonte
Essa pergunta é melhor feita no Stack Overflow, pois há pouco sobre isso específico dos jogos.
21810 Chris
31
@ Chris: Discordo totalmente: a pergunta menciona especificamente jogos! Os problemas encontrados quando você precisa enviar uma atualização a cada 16 ms são muito diferentes dos encontrados na maioria dos aplicativos de desktop.
Andrew Russell
A questão não é clara. Java e C # diferem o suficiente para que apenas conselhos muito gerais sejam aplicáveis ​​a ambos. Todas as respostas até agora foram em C #. Também plataforma alvo não mencionada - bons conselhos podem diferir dependendo do dispositivo (por exemplo, programação para um telefone celular, zune, xbox, diferente da programação para um PC). Foi até uma pergunta bastante ampla que alguém respondeu que os próprios idiomas gerenciados são a "armadilha".
Paulecoyote
@ paulecoyote: mudei para pergunta para perguntar apenas sobre C #. Além disso, como não há plataforma específica mencionada, trata-se do PC.
Michael Klement
O @ Michael assumindo que a plataforma é uma suposição perigosa, pois diferem muito na implementação, seria bom mencionar o Windows especificamente e soltar "like" e "Java" completamente.
21910 paulecoyote

Respostas:

69

Eu não sei muito sobre Java, então isso é do ponto de vista de um desenvolvedor .net.

O maior de longe é o lixo. O coletor de lixo .NET no Windows faz um trabalho fantástico e você pode se safar sem que o bebê fique sentado na maior parte do tempo. No Xbox / Windows Phone 7, isso é diferente. Se você receber paradas a cada poucos quadros, a coleta de lixo poderá estar causando problemas. No momento, ele é acionado após cada alocação de 1 MB.

Aqui estão algumas dicas para lidar com o lixo. Você não precisa se preocupar com a maioria deles na realidade, mas eles podem ser úteis um dia.

  • Desenhe o conteúdo de GC.GetTotalMemory()para a tela. Isso fornece uma aproximação da quantidade de bytes alocados que seu jogo usa. Se dificilmente se mover, você está indo bem. Se está subindo rápido, você tem problemas.
  • Tente alocar todos os seus objetos de heap antecipadamente. Se você não alocar tudo antes do início do jogo, toda vez que você acertar um monte de alocações, você ficará paralisado. Sem alocações, sem coleções. Tão simples como isso.
  • Após o carregamento, ligue GC.Collect(). Se você sabe que a maioria de suas grandes alocações está fora do caminho, é bom informar o sistema.
  • NÃO CHAME GC.Collect()todos os quadros. Pode parecer uma boa idéia, manter o controle sobre o lixo e tudo mais, mas lembre-se de que o único com pior que uma coleta de lixo é a coleta de lixo.
  • Procure de onde vem o seu lixo. Existem algumas causas comuns, como concatenar cadeias de caracteres em vez de usar StringBuilder(cuidado, StringBuildernão é uma bala mágica e ainda pode causar alocações ! . Isso significa que operações simples como adicionar um número ao final de uma cadeia de caracteres podem criar quantidades surpreendentes de lixo) ou o uso de foreachloops sobre coleções que usam a IEnumerableinterface também pode criar lixo sem que você saiba (por exemplo, foreach (EffectPass pass in effect.CurrentTechnique.Passes)é comum)
  • Use ferramentas como o criador de perfil de memória CLR para descobrir onde a memória está sendo alocada. Existem muitos tutoriais por aí sobre como usar essa ferramenta.
  • Quando você souber onde está sua alocação durante o jogo, veja se pode usar truques como agrupar objetos para diminuir essa contagem de alocação.
  • Se tudo mais falhar, faça suas coleções rodarem mais rápido! O GC na estrutura compacta segue todas as referências no seu código para descobrir quais objetos não estão mais em uso. Refatore seu código, use menos referências!
  • Lembre-se de usar IDisposableem classes que contêm recursos não gerenciados. Você pode usá-los para limpar a memória que o GC não pode se libertar.

A outra coisa a se pensar é o desempenho do ponto flutuante. Enquanto o .NET JITer faz uma quantidade razoável de otimizações específicas do processador, ele não pode usar o SSE ou qualquer outro conjunto de instruções SIMD para acelerar sua matemática de ponto flutuante. Isso pode causar uma grande diferença de velocidade entre C ++ e C # para jogos. Se você estiver usando mono, eles têm algumas bibliotecas especiais de matemática do SIMD das quais você pode tirar proveito.

Cubed2D
fonte
Concordo completamente. As implementações do coletor de lixo nas plataformas "menores" parecem ser lixo completo.
Krisc 19/07/10
Bom post, no entanto no que diz respeito à sua declaração sobre a "alocação de pilha objetos na frente" Eu recomendo a leitura A verdade sobre tipos de valor
Justin
Ótimo ponto, quando você otimiza o código, vale a pena entender a plataforma que você está tentando otimizar. Esse não era realmente um comentário sobre tipos de valor / tipos de referência, é provável que nenhum objeto de vida longa esteja na pilha. É realmente para garantir que você consiga o máximo possível de suas alocações no tempo de carregamento, para que você não atinja a barreira mágica de 1 MB durante o jogo. Todas as implementações .net que o xna tem como alvo também têm uma garantia pura, os objetos alocados próximos no tempo estarão próximos no espaço, o que pode ser bom para o desempenho.
Cubed2D
Também parece que eu esqueci de mencionar que a atual estrutura compacta no xbox é alérgica a chamadas de método embutidas. No entanto, mantenha esse para o seu pior cenário de desempenho, usar as versões ref dos métodos matemáticos parece bastante feio!
Cubed2D
10

Uma armadilha típica de desempenho não está considerando o coletor de lixo no design / desenvolvimento do jogo. Produzir muito lixo pode levar a "soluços" no jogo, que acontecem quando o GC é executado por um tempo considerável.

Para C #, o uso de objetos de valor e a instrução "using" podem aliviar a pressão do GC.

Matias Valdenegro
fonte
3
Além disso, você pode dizer ao Garbage Collector para executar explicitamente se você acabou de terminar um loop pesado de alocação / livre ou se achar que tem ciclos de reposição.
BarrettJ
16
A usingdeclaração não tem nada a ver com coleta de lixo! É para IDisposableobjetos - que são para liberar recursos não gerenciados (ou seja, aqueles que não são manipulados pelo coletor de lixo ).
Andrew Russell
9

Eu diria que os maiores problemas que encontrei ao escrever jogos em C # tem sido a falta de bibliotecas decentes. A maioria das que encontrei são portas diretas, mas incompletas, ou wrappers em uma biblioteca C ++ que incorrem em uma penalidade de alto desempenho por empacotamento. (Estou falando especificamente do MOgre e Axiom para a biblioteca OGRE e do BulletSharp para a biblioteca de física Bullet)

As linguagens gerenciadas (distintas de Interpretadas - nem Java nem C # são mais interpretadas) podem ser tão rápidas quanto as linguagens nativas se você tiver um bom entendimento do que realmente as torna lentas (empacotamento, coleta de lixo). O problema real, eu acho, é que os desenvolvedores de bibliotecas ainda não perceberam isso.

Karantza
fonte
Isso é um bom ponto sobre C # e Java a ser gerido em vez de interpretar ... editado a minha pergunta para torná-la mais precisa :)
Michael Klement
2
O Marshalling em geral é um gargalo de desempenho, mas também ajuda a estar ciente dos tipos blittable - tipos que podem ser mapeados diretamente para a memória não gerenciada sem impacto significativo no desempenho. msdn.microsoft.com/pt-br/library/75dwhxf7.aspx
Sean Edwards
8

Como outros já disseram, as pausas na coleta de GC são o maior problema. Usar pools de objetos é uma solução típica.

maravilhoso
fonte
4

C # e Java não são interpretados. Eles são compilados em um bytecode intermediário que, após o JIT , se torna tão rápido quanto o código nativo (ou próximo o suficiente para ser insignificante)

A maior armadilha que encontrei está na liberação de recursos que afetam diretamente a experiência do usuário. Essas linguagens não suportam automaticamente a finalização determinística, como o C ++, que, se você não espera, pode levar a malhas flutuando sobre a cena depois que você pensou que elas foram destruídas. (C # realiza finalização determinística através de IDisposable , não sei o que o Java faz.)

Fora isso, os idiomas gerenciados são realmente muito mais capazes de lidar com o tipo de desempenho exigido pelos jogos do que os que recebem. Um código gerenciado bem escrito é muito mais rápido que um código nativo mal escrito.

Sean Edwards
fonte
Sim, obrigado pela observação, já corrigimos a coisa interpretada / gerenciada;) Além disso, bom ponto com as malhas flutuando pela cena. Não tinha pensado nisso quando se pensa em problemas GC ...
Michael Klement
11
IDisposablepermite limpeza determinística de recursos críticos e não gerenciados, mas não afeta diretamente a finalização ou o coletor de lixo.
Sam Harwell
4

Nem Java nem C # são interpretados. Ambos são compilados no código de máquina nativo.

O maior problema deles e dos jogos é ter que codificar de forma que eles nunca colecionem lixo durante o jogo. O número de aros pelos quais você precisa avançar para alcançar isso quase supera os benefícios de usá-los em primeiro lugar. A maioria dos recursos que tornam esse idioma divertido de usar para a programação de aplicativos ou servidores deve ser evitada na programação de jogos, caso contrário, você terá longas pausas durante o jogo à medida que disparam e coletam lixo.

gman
fonte
A coleta de lixo pode ser tratada, pelo menos em C #, razoavelmente bem, então eu não diria que é uma quebra de negócio. Com o encadeamento adequado e a conscientização do estado do programa, você pode evitar problemas de desempenho. Ainda é outra coisa em que você deve pensar e tornar o idioma gerenciado um pouco menos gerenciado.
22410 Karantza
4
Vale ressaltar que no .NET 4, o coletor de lixo suporta a coleta em segundo plano para todas as três gerações de objetos. Isso deve minimizar efetivamente o impacto no desempenho da coleta de lixo nos jogos. Link relevante: geekswithblogs.net/sdorman/archive/2008/11/07/…
Mike Strobel
Os coletores de lixo estão evoluindo e, como observado por Mike Strobel, alguns já estão em produção que quase eliminam essa armadilha.
Sam Harwell
2
Nem C # nem Java são compilados para código de máquina nativo. C # compila para MSIL e Java para bytecode. A coleta de lixo não dará pausas "longas", mas pode causar "soluços".
Cloudanger
2

Uma grande armadilha que vejo ao criar jogos com linguagens como essas (ou usar ferramentas como XNA, mecanismo TorqueX etc.) é que você terá dificuldade em encontrar uma equipe de pessoas boas com a experiência necessária para criar um jogo equivalente ao que seria bastante fácil encontrar pessoas em C ++ e OpenGL / DirectX.

A indústria de desenvolvedores de jogos ainda é muito rica em C ++, pois a maioria das ferramentas e pipelines que são usados ​​para promover grandes ou pequenos jogos polidos foram escritos em C ++ e, tanto quanto eu sei, TODOS os kits de desenvolvimento oficiais que você pode get para XBox, PS3 e Wii são lançados apenas com compatibilidade para C ++ (o conjunto de ferramentas XBox pode ser mais gerenciado atualmente, alguém sabe mais?)

Se você deseja desenvolver jogos para consoles agora, você obtém praticamente XNA e C # no XBox e somente em uma parte lateral da biblioteca de jogos chamada XBox Live Indie Games. Alguns que vencem concursos etc. são escolhidos para portar seu jogo para o verdadeiro XBox Live Arcade. Fora esse plano de fazer um jogo para PC.

mikeschuld
fonte