Eu entendo que às vezes você precisa de propriedades, como:
public int[] Transitions { get; set; }
ou:
[SerializeField] private int[] m_Transitions;
public int[] Transitions { get { return m_Transitions; } set { m_Transitions = value; } }
Mas minha impressão é que, no desenvolvimento de jogos, a menos que você tenha uma razão para fazer o contrário, tudo deve ser o mais rápido possível. Minha impressão das coisas que li é que o Unity 3D não tem certeza de acessar o acesso via propriedades, portanto, usar uma propriedade resultará em uma chamada de função extra.
Posso ignorar constantemente as sugestões do Visual Studio para não usar campos públicos? (Observe que usamos int Foo { get; private set; }
onde há uma vantagem em mostrar o uso pretendido.)
Estou ciente de que a otimização prematura é ruim, mas como eu disse, no desenvolvimento de jogos você não faz algo lento quando um esforço semelhante pode torná-lo rápido.
fonte
ArrayList
). Isso ocorre porque C não possui genéricos e esses programadores de C provavelmente acharam mais fácil reimplementar repetidamente listas vinculadas do que fazer o mesmo noArrayLists
algoritmo de redimensionamento. Se você tiver uma boa otimização de baixo nível, encapsule-a!Respostas:
Não necessariamente. Assim como no software aplicativo, há um código em um jogo que é crítico para o desempenho e código que não é.
Se o código for executado milhares de vezes por quadro, essa otimização de baixo nível poderá fazer sentido. (Embora você possa primeiro verificar primeiro se é realmente necessário chamá-lo com frequência. O código mais rápido é o código que você não executa)
Se o código for executado a cada dois segundos, a legibilidade e a manutenção são muito mais importantes que o desempenho.
Com propriedades triviais no estilo de
int Foo { get; private set; }
você, pode ter certeza de que o compilador otimizará o wrapper de propriedade. Mas:GetFoo()
/ explícitoSetFoo(value)
: implicar que há muito mais acontecendo nos bastidores. (ou se eu precisar ter ainda mais certeza de que outros entendem a dica:CalculateFoo()
/SwitchFoo(value)
)fonte
Update
completamente economizará mais tempo do queUpdate
acelerar.Em caso de dúvida, use as melhores práticas.
Você está em dúvida.
Essa foi a resposta fácil. A realidade é mais complexa, é claro.
Primeiro, existe o mito de que a programação de jogos é uma programação de desempenho super super alto e tudo tem que ser o mais rápido possível. Classifico a programação de jogos como uma programação que reconhece o desempenho . O desenvolvedor deve estar ciente das restrições de desempenho e trabalhar dentro delas.
Segundo, existe o mito de que tornar tudo o mais rápido possível é o que faz um programa rodar mais rápido. Na realidade, identificar e otimizar gargalos é o que torna um programa mais rápido, e isso é muito mais fácil / mais barato / mais rápido, se o código é fácil de manter e modificar; código extremamente otimizado é difícil de manter e modificar. Daqui resulta que , para escrever programas extremamente otimizados, você precisa usar a otimização extrema do código sempre que necessário e o mais raramente possível.
Terceiro, o benefício das propriedades e dos acessadores é que eles são robustos para serem alterados e, assim, facilitam a manutenção e a modificação do código - o que é algo que você precisa para escrever programas rápidos. Deseja lançar uma exceção sempre que alguém deseja definir seu valor como nulo? Use um setter. Deseja enviar uma notificação sempre que o valor for alterado? Use um setter. Deseja registrar o novo valor? Use um setter. Deseja fazer uma inicialização lenta? Use um getter.
E quarto, pessoalmente, não sigo as melhores práticas da Microsoft neste caso. Se eu precisar de uma propriedade com informações triviais e públicas getters e getters , basta usar um campo e simplesmente alterá-lo para uma propriedade quando necessário. No entanto, raramente preciso de uma propriedade tão trivial - é muito mais comum querer verificar condições de contorno ou tornar o setter privado.
fonte
É muito fácil para o tempo de execução .net incorporar propriedades simples, e geralmente o faz. Porém, esse inlining é normalmente desativado por criadores de perfil, portanto, é fácil ser enganado e pensar que alterar uma propriedade pública para um campo público acelerará o software.
No código que é chamado por outro código que não será recompilado quando o código for recompilado, é recomendável usar propriedades, pois a alteração de um campo para uma propriedade é uma alteração de quebra. Mas, para a maioria dos códigos, além de poder definir um ponto de interrupção no get / set, nunca vi benefícios em usar uma propriedade simples em comparação com um campo público.
A principal razão pela qual usei propriedades em meus 10 anos como programador profissional de C # foi parar outros programadores me dizendo que eu não estava mantendo o padrão de codificação. Essa foi uma boa troca, já que quase nunca havia uma boa razão para não usar uma propriedade.
fonte
Estruturas e campos simples facilitarão a interoperabilidade com algumas APIs não gerenciadas. Frequentemente, você descobrirá que a API de baixo nível deseja acessar os valores por referência, o que é bom para o desempenho (como evitamos uma cópia desnecessária). O uso de propriedades é um obstáculo para isso e, muitas vezes, as bibliotecas do wrapper fazem cópias para facilitar o uso e, às vezes, para segurança.
Por esse motivo, você geralmente obtém melhor desempenho com tipos de vetores e matrizes que não têm propriedades, mas campos nus.
As melhores práticas não estão criando no vácuo. Apesar de algum culto à carga, em geral as melhores práticas existem por um bom motivo.
Nesse caso, temos alguns:
Uma propriedade permite alterar a implementação sem alterar o código do cliente (em nível binário, é possível alterar um campo para uma propriedade sem alterar o código do cliente no nível de origem, no entanto, ele será compilado para algo diferente após a alteração) . Isso significa que, usando uma propriedade desde o início, o código que faz referência à sua não precisará ser recompilado apenas para alterar o que a propriedade faz internamente.
Se nem todos os valores possíveis dos campos do seu tipo forem estados válidos, você não deseja expô-los ao código do cliente que o modifica. Portanto, se algumas combinações de valores forem inválidas, você deseja manter os campos privados (ou internos).
Eu tenho dito o código do cliente. Isso significa código que chama no seu. Se você não está criando uma biblioteca (ou mesmo fazendo uma biblioteca, mas usando interno, em vez de público), geralmente pode se safar e ter uma boa disciplina. Nessa situação, a melhor prática de usar propriedades está presente para impedir que você atire no próprio pé. Além disso, é muito mais fácil raciocinar sobre o código se você puder ver todos os lugares onde um campo pode mudar em um único arquivo, em vez de precisar se preocupar com o que está sendo modificado ou não em outro lugar. De fato, as propriedades também são boas para colocar pontos de interrupção ao descobrir o que deu errado.
Sim, há valor em ver o que está sendo feito na indústria. No entanto, você tem motivação para ir contra as melhores práticas? ou você está apenas indo contra as melhores práticas - dificultando o raciocínio do código - apenas porque alguém fez isso? Ah, a propósito, "outros fazem isso" é como você inicia um culto à carga.
Então ... O seu jogo está lento? É melhor dedicar tempo para descobrir o gargalo e corrigi-lo, em vez de especular o que poderia ser. Você pode ter certeza de que o compilador fará muitas otimizações; por isso, é provável que você esteja procurando o problema errado.
Por outro lado, se você está decidindo o que fazer para começar, deve se preocupar com quais algoritmos e estruturas de dados primeiro, em vez de se preocupar com detalhes menores, como campos x propriedades.
Finalmente, você ganha alguma coisa contrariando as melhores práticas?
Para os detalhes do seu caso (Unity e Mono para Android), o Unity usa valores por referência? Caso contrário, ele copiará os valores de qualquer maneira, sem ganho de desempenho.
Se isso acontecer, se você estiver passando esses dados para uma API que leva ref. Faz sentido tornar o campo público ou você pode tornar o tipo capaz de chamar a API diretamente?
Sim, é claro, poderia haver otimizações que você poderia fazer usando estruturas com campos nus. Por exemplo, você os acessa com
ponteirosSpan<T>
ou similar. Eles também são compactos na memória, facilitando a serialização para envio pela rede ou armazenamento permanente (e sim, são cópias).Agora, você escolheu os algoritmos e estruturas corretos, se eles se tornarem um gargalo, então você decide qual é a melhor maneira de corrigi-lo ... que poderia ser estruturas com campos nus ou não. Você poderá se preocupar com isso se e quando acontecer. Enquanto isso, você pode se preocupar com assuntos mais importantes, como tornar um jogo bom ou divertido que vale a pena jogar.
fonte
:
Você está certo de estar ciente de que a otimização prematura é ruim, mas isso é apenas metade da história (veja a citação completa abaixo). Mesmo no desenvolvimento de jogos, nem tudo precisa ser o mais rápido possível.
Primeiro faça funcionar. Somente então, otimize os bits que precisam. Isso não quer dizer que o primeiro rascunho possa ser confuso ou desnecessariamente ineficiente, mas que a otimização não é uma prioridade neste estágio.
" O verdadeiro problema é que os programadores passaram muito tempo se preocupando com a eficiência nos lugares errados e nos horários errados ; a otimização prematura é a raiz de todo mal (ou pelo menos a maior parte) na programação". Donald Knuth, 1974.
fonte
Para coisas básicas como essa, o otimizador de C # vai acertar de qualquer maneira. Se você se preocupa com isso (e se preocupa com mais de 1% do seu código, está fazendo errado ), um atributo foi criado para você. O atributo
[MethodImpl(MethodImplOptions.AggressiveInlining)]
aumenta muito a chance de um método ser incorporado (getters e setters de propriedades são métodos).fonte
A exposição de membros do tipo estrutura como propriedades, em vez de campos, geralmente resultará em baixo desempenho e semântica, especialmente nos casos em que as estruturas são realmente tratadas como estruturas, e não como "objetos peculiares".
Uma estrutura no .NET é uma coleção de campos gravados juntos e que, para alguns propósitos, podem ser tratados como uma unidade. O .NET Framework foi projetado para permitir que estruturas sejam usadas da mesma maneira que objetos de classe, e há momentos em que isso pode ser conveniente.
Grande parte dos conselhos que cercam as estruturas se baseia na noção de que os programadores desejam usar estruturas como objetos, e não como coleções de campos colados. Por outro lado, há muitos casos em que pode ser útil ter algo que se comporte como um monte de campos colados. Se é disso que se precisa, o conselho do .NET adicionará trabalho desnecessário para programadores e compiladores, produzindo desempenho e semântica piores do que simplesmente usar estruturas diretamente.
Os maiores princípios a serem considerados ao usar estruturas são:
Não passe ou retorne estruturas por valor ou faça com que elas sejam copiadas desnecessariamente.
Se o princípio 1 for respeitado, as estruturas grandes terão um desempenho tão bom quanto as pequenas.
Se
Alphablob
é uma estrutura que contém 26 públicasint
campos nomeados az, e uma propriedade getterab
que retorna a soma dos seusa
eb
campos, então, dadoAlphablob[] arr; List<Alphablob> list;
, o códigoint foo = arr[0].ab + list[0].ab;
precisará ler camposa
eb
dearr[0]
, mas vai precisar de ler todos os 26 campos delist[0]
mesmo que ele vai ignore todos, exceto dois. Se alguém quiser ter uma coleção genérica do tipo lista que possa funcionar eficientemente com uma estrutura semelhantealphaBlob
, substitua o getter indexado por um método:que então invocaria
proc(ref backingArray[index], ref param);
. Dada essa coleção, se alguém substituirsum = myCollection[0].ab;
porisso evitaria a necessidade de copiar partes do
alphaBlob
que não serão usadas no getter de propriedades. Como o delegado transmitido não acessa nada além de seusref
parâmetros, o compilador pode transmitir um delegado estático.Infelizmente, o .NET Framework não define nenhum dos tipos de delegados necessários para que isso funcione bem, e a sintaxe acaba sendo uma bagunça. Por outro lado, essa abordagem permite evitar a necessidade de copiar estruturas desnecessariamente, o que tornará prático executar ações no local em grandes estruturas armazenadas em matrizes, que é a maneira mais eficiente de acessar o armazenamento.
fonte
list
e o getter de propriedade paraab
estiverem alinhados, pode ser possível para um JITter reconhecer que apenas membrosa
eb
são necessários, mas tenho conhecimento de qualquer versão jitter realmente fazendo essas coisas.