Como escrever código de C # muito rápido | Mark Farragher | Skillshare

Velocidade de reprodução


  • 0.5x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 2x

Como escrever código de C # muito rápido

teacher avatar Mark Farragher, Microsoft Certified Trainer

Assista a este curso e milhares de outros

Tenha acesso ilimitado a todos os cursos
Oferecidos por líderes do setor e profissionais do mercado
Os temas incluem ilustração, design, fotografia e muito mais

Assista a este curso e milhares de outros

Tenha acesso ilimitado a todos os cursos
Oferecidos por líderes do setor e profissionais do mercado
Os temas incluem ilustração, design, fotografia e muito mais

Aulas neste curso

23 aulas (3 h 43 min)
    • 1. Dicas de desempenho de C#

      2:59
    • 2. Introdução à otimização de código

      6:12
    • 3. O que é memória de pilha?

      5:23
    • 4. O que é memória de monte?

      6:18
    • 5. Quais são tipos de valor?

      5:50
    • 6. Quais são tipos de referência?

      5:29
    • 7. O que é boxe e unboxing?

      6:28
    • 8. O que são cordas imutáveis?

      6:43
    • 9. Um curso de falha em linguagem intermediária

      14:45
    • 10. Dica #1: evite boxe e and

      9:39
    • 11. Dica #2: adicione cordas de forma eficiente

      8:20
    • 12. Dica #3: use o curso de lista correto

      8:29
    • 13. Dica #4: use o tipo de matriz correto

      9:09
    • 14. Dica #5: evite jogar exceções

      14:55
    • 15. Dica #6: use para em vez de foreach

      16:40
    • 16. Como funciona o coletor de lixo?

      16:07
    • 17. Dica #7: otimize para coleta de lixo

      18:26
    • 18. Dica #8: use delegados rápidos

      9:13
    • 19. Dica #9: construa uma fábrica de cursos rápidos

      17:06
    • 20. Os arrays na pilha valem o problema?

      11:28
    • 21. Como faço para usar ponteiros no C#?

      10:05
    • 22. Dica #10: acelere o código com ponteiros

      11:43
    • 23. Recolher de curso

      1:44
  • --
  • Nível iniciante
  • Nível intermediário
  • Nível avançado
  • Todos os níveis

Gerado pela comunidade

O nível é determinado pela opinião da maioria dos estudantes que avaliaram este curso. Mostramos a recomendação do professor até que sejam coletadas as respostas de pelo menos 5 estudantes.

193

Estudantes

--

Sobre este curso

78e5abec

Você sabe como escrever código C# rápido?

Você pode já ter matriculado em um curso de programação C# ou aprender o idioma na escola ou universidade. Mas eis um fato sóbrio: a maioria dos cursos ensinam apenas a escrever código, não a escrever código rápido.

O . O NET Framework é enorme. Para qualquer problema, existem muitas soluções, e nem sempre é claro qual solução é a melhor escolha.

Você sabia que adicionar cordas juntas da maneira errada retardará seu código por um fator de mais de duzentos? E se você não estiver lidando com exceções da maneira certa, seu código executará milhares de vezes mais lentos que o normal.

Código de C# lento é um grande problema. Código lento na web não vai escalar para milhares de usuários. Código lento tornará sua interface de usuário inutilizável. Código lento vai tornar seus aplicativos móveis definharem na loja de aplicativos.

Código lento está retendo você de volta!

Posso ajudá-lo.

Em uma série de palestras curtas, vou cobrir muitos gargalos de desempenho comuns. Vou introduzir cada problema e escrever um pequeno programa de teste para medir o desempenho de linha de base. Em seguida, vou demonstrar cada solução possível e avaliar como cada solução se mede melhor.

Mas há mais! Vou também mergulhar no Código Intermediário Comum (CIL), a linguagem que o compilador C# compila. Se isso soar assustador, não se preocupe! O idioma CIL é realmente muito fácil de ler e entender. Vou levar você ao longo dos conceitos básicos em uma palestra rápida de 15 minutos.

Ser capaz de ler código CIL é uma habilidade muito útil que vai ajudar você a evitar muitas armadilhas de desempenho e dar uma compreensão mais profunda do código . Estrutura NET.

Por que você deve fazer este curso?

Você deve fazer este curso se for desenvolvedor de C# iniciante ou intermediário e quiser levar suas habilidades para o próximo nível. Todas as minhas palestras são muito fáceis de acompanhar e eu explico todos os tópicos com código claro e muitos diagramas de instruções.

Ou você pode estar trabalhando em uma seção crítica de código em um projeto de C# e precisa tornar seu código funcionado o mais rápido possível. As dicas e truques deste curso vão ajudar você imensamente.

Ou talvez você esteja se preparando para uma entrevista de trabalho relacionada ao C#? Este curso vai dar a você uma excelente base para responder a quaisquer perguntas relacionadas ao desempenho que possam jogar em você.

Conheça seu professor

Teacher Profile Image

Mark Farragher

Microsoft Certified Trainer

Professor

Mark Farragher is a blogger, investor, serial entrepreneur, and the author of 11 successful Udemy courses. He has been a Founder and CTO, and has launched two startups in the Netherlands. Mark became a Microsoft Certified Trainer in 2005. Today he uses his extensive knowledge to help tech professionals with their leadership, communication, and technical skills.

Visualizar o perfil completo

Nota do curso

As expectativas foram atingidas?
    Superou!
  • 0%
  • Sim
  • 0%
  • Um pouco
  • 0%
  • Não
  • 0%
Arquivo de avaliações

Em outubro de 2018, atualizamos nosso sistema de avaliações para melhorar a forma como coletamos feedback. Abaixo estão as avaliações escritas antes dessa atualização.

Por que fazer parte da Skillshare?

Faça cursos premiados Skillshare Original

Cada curso possui cursos curtas e projetos práticos

Sua assinatura apoia os professores da Skillshare

Aprenda em qualquer lugar

Faça cursos em qualquer lugar com o aplicativo da Skillshare. Assista no avião, no metrô ou em qualquer lugar que funcione melhor para você, por streaming ou download.

Transcrições

1. Dicas de desempenho C#: Deixa-me fazer-te uma pergunta. Você gostaria de se tornar um arquiteto de desempenho C sharp? Tenho que admitir, inventei essa palavra. Tenho que admitir, Mas no meu livro, um arquiteto de desempenho C afiado é um desenvolvedor sênior que escreve alto desempenho C casaco afiado . Então, um desenvolvedor sênior que está agudamente ciente de técnicas de otimização de revestimento em direitos, chamado para jogos para análise de dados para aquisição de dados em tempo real para todos esses ambientes legais onde o revestimento rápido é essencial, Então, você gostaria de tornar-se um arquiteto desempenho C afiada? Gostaria de se juntar ao clube, então? Este é o curso para você. Neste curso, vou ensinar-lhe uma língua fora hacks otimização. Vamos olhar para a otimização básica. Então esta é a fruta baixa pendurada, os ajustes fáceis que farão com que seu código seja executado até 1000 vezes mais rápido. Bem, olhe para a otimização de intermediários. Então estes são anéis reflexos bastante avançados de seus códigos. Isso lhe dará uma pequena melhoria de desempenho. Vamos olhar para alguma otimização realmente avançada é que vamos olhar para usar ponteiros em C nítido em escrever diretamente na memória em alguns cenários. Isso é mais rápido do que usar as classes .net padrão. Vamos olhar para a base fora do tempo de execução dot net, então eu vou dar-lhe um curso intensivo na pilha no heap. Como tipos de valor e tipos de referência de armazenamentos na pilha, no heap sobre como os dados se movem entre a pilha no heap. Quando estivermos correndo casaco, vou te ensinar a linguagem da mídia, então eu vou realmente levar códigos de conformidade em linguagem intermediária. Mostre essa linguagem intermediária para você, e eu explicarei o que todas as instruções estão fazendo. Assim, até o final do curso, você será capaz de ler linguagem intermediária em Você vai se tornar ciente de como este compilador de edição compilado. Lojas se envolvem em linguagem intermediária sobre como certas instruções podem abrandar sua costa. Então este é um curso bastante grande. Ele contém muitas e muitas palestras lá questionários para testar o seu conhecimento sobre. Você pode baixar o código-fonte que eu tenho usado para benchmarking. Então, você gostaria de se tornar um arquiteto de desempenho C Sharp? Então este é o curso para você. Então eu criei este curso para desenvolvedores de lojas de nível médio ou sênior ver que querem aprender a escrever rápido e eficiente casaco C afiado para obter sua carreira pronta para assuntos legais , como desenvolvimentos de jogos ou aquisição de dados em tempo real. Obrigado por ouvir. Estou ansioso para recebê-lo no curso. 2. Introdução à otimização de códigos: Então vamos falar de otimização de códigos. O que é otimização de código? Bem, otimização de contagem em geral implica modificar no sistema I t para fazê-lo funcionar de forma mais eficiente ou usar menos recursos ou ser mais robusto. Por exemplo, um programa de computador pode ser otimizado para que ele irá executar mais rápido ou usar menos memória ou armazenamento em disco ou ser mais responsivo em termos fora da interface do usuário. Otimizamos o código para evitar lentidões inaceitáveis. O código do vencedor está em uso. Imagine clicar em um botão rótulos mostrar relatório e, em seguida, ter que esperar 10 minutos antes qualquer coisa apareça na interface de usuário aceitável. O atraso é de dois segundos, então precisamos encontrar uma maneira de gerar esse relatório em apenas dois segundos. Também otimizamos para tornar nossos códigos escaláveis. Eu pessoalmente experimentei sites que funcionam bem com 10 usuários simultâneos, mas completamente quebrar quando 50 usuários passaram a visitar o site, tudo ao mesmo tempo. Há até um nome para isso. É chamado de nomes de efeito filha barra para quando os sites populares barra DOT apresenta um site em sua primeira página, que então prontamente falha porque milhões de visitantes clicam no hiperlink em sobrecarga. O servidor Web, uma música que o seu conhecimento fora da otimização de código vai ajudá-lo a rebocar a evitar estes potenciais desastres. Então, neste curso, nós vamos olhar. É assim que um programa é executado mais rápido. Temos uma série de estratégias à nossa disposição. Para atingir esse objetivo, podemos reescrever algoritmos para alcançar o mesmo resultado. Com menos frio, podemos evitar instruções desnecessárias. Podemos nos certificar de que as bibliotecas que usamos são projetadas especificamente para a tarefa em questão. Podemos reduzir o consumo de memória, tanto quanto possível, e podemos evitar cenários onde o código está bloqueando, aguardando um recurso lento. A otimização de desempenho neste curso se enquadra em uma ou mais dessas cinco categorias. Mas antes de começarmos, uma última palavra de aviso. Há citações famosas de Michael Jackson no que quero dizer o cientista da computação britânico Michael Jackson, não o outro cara. A citação é assim. A primeira regra fora da otimização do programa é. Não faça isso. A segunda divisão fora da otimização do programa, que é apenas para especialistas, é não fazê-lo ainda. O famoso cientista da computação Donald Neuf tinha roupas semelhantes. Otimização prematura é a raiz de todo o mal. O pensamento por trás dessas citações é que seu código irá completamente para ajudar. Se você tentar se concentrar na otimização de desempenho desde o início enquanto você ainda está trabalhando em seu programa, seu casaco é fluidos e evolui em direções inesperadas. É impossível prever de forma confiável com antecedência onde serão os gargalos de desempenho. Aqui está um exemplo perto do final deste curso, você vai aprender que ponteiros podem acelerar o seu casaco, mas apenas se você usá-los de uma forma muito específica. Então imagine fator re em todo o seu programa para usar ponteiros, e, em seguida, um par de semanas depois, você precisa refletir sobre o seu algoritmo e para baixo. O ponto de otimização não funciona mais. Então agora você está preso com um casaco inseguro, difícil de ler que é na verdade mais lento do que um casaco bem escrito, limpo e seguro. Isso não usa ponteiros, então você provavelmente vai ter que desfazer tudo. Outro exemplo. Você passou semanas espremendo cada onça possível de desempenho de uma função de biblioteca e, eventualmente, conseguiu otimizar a função para ser executada em menos de um milissegundos. Parabéns, então seu programa evolui novamente, e quando você entrar em produção, sua mensagem é chamada 99% do tempo, logo após um núcleo de banco de dados que leva 300 milissegundos para ser concluída. Então toda a sua otimização zwart de Nada. Portanto, o curso de ação recomendado é escrever código modular simples e claro em Deixar a organização até o fim, quando você sabe de forma confiável onde os gargalos de desempenho serão. No entanto, existem exceções a esta regra. Vou deixá-los com a versão completa da citação do Donald. Devemos esquecer as pequenas eficiências, digamos cerca de 97% das vezes. Otimização prematura é a raiz do mal. No entanto, não devemos deixar passar são oportunidades em que 3% críticos. Então, se você pode ver claramente os 3% desde o início, sinta-se livre para começar a otimizar imediatamente. 3. O que é memória de empilhada?: memória pilha era simplesmente a pilha é um bloco fora da memória que é usado para chamar métodos e armazenar variáveis locais. Então deixe-me desenhar isso aqui no quadro. Vou desenhar a pilha como uma coluna vertical como esta. Não, quando eu começar a executar algum código, essa pilha estará inicialmente vazia. Mas quando Mike Oates chama os métodos isso acontece, os parâmetros método o endereço de retorno em todas as variáveis locais fora dos métodos são colocados na pilha. Todo esse bloco de dados é chamado de quadro de pilha. Então, o que acontece com a pilha quando meus códigos chama outro método de dentro deste método, que isso a mesma coisa acontece novamente? Os parâmetros do método retornam o endereço em variáveis locais fora do novo método que eu coloquei na pilha direita em cima do quadro de pilha anterior. Então é por isso que se chama pilha. A informação é empilhada uma sobre a outra. O que acontece quando meu culto encontra declaração de retorno? Como você provavelmente sabe, uma instrução return termina um método e retorna ao código de chamada. Agora, na pilha. Isto é o que acontece. Todo o quadro de pilha que correspondeu ao método é removido, mas você pode estar pensando o que aconteceu com todas as variáveis locais que foram armazenadas no quadro de pilha? Bem, todos eles estão fora do alcance, o que é apenas uma maneira chique de dizer que eles estão destruídos. Portanto, este é um fato importante para lembrar no momento em que você retorna fora dos métodos todas as suas variáveis locais fora de que os métodos saem do escopo e são destruídos. Se eu continuar no meu programa e também retornar fora dos primeiros métodos, estamos de volta para onde começamos com uma pilha vazia. Você pode estar se perguntando o que acontece se um método chama em outros métodos, que causam outro método, que chama outros métodos 1000 vezes bem, a pilha rapidamente preencheria com quadros de pilha até que haja espaço normal. Eventualmente, o estoque estará completamente cheio na estrutura líquida DOT lança pilha. Exceção de estouro. Se você vir esta mensagem de erro, isso significa que você tem uma seqüência infinita off método chamadas em algum lugar em seu código. Vamos dar uma olhada em algum código. Escrevi um programa simples para desenhar um quadrado na tela. Você vê que eu tenho um método quadrado gaveta que chama um método de linha de gaveta quatro vezes para desenhar os quatro lados de um quadrado. Vou colocar uma pausa, pontos dentro da gaveta, métodos de linha e, em seguida, executar o meu resfriado. Veja isto. Agora, neste ponto do meu casaco, aquela pilha vai ficar assim. Minha primeira chamada para desenhar quadrado está aqui na parte inferior da pilha com seus quatro parâmetros, endereço de retorno e variáveis locais. Em seguida é a chamada para uma linha de gaveta com novamente quatro parâmetros. Retornar endereço ativado. Nesse caso, nenhuma variável local porque a linha de gaveta não tem nenhuma no Visual Studio. Você pode dar uma olhada nessa pilha abrindo a exibição de pilha de chamadas, que está aqui. Você pode ver a chamada em desenhar quadrado e, em seguida, em uma linha de gaveta. Então, esta janela mostra quais quadros de pilha são armazenados na pilha. Agora, como demonstração final, deixe-me mostrar-lhe uma pilha. Exceção de estouro. Deixe-me modificar meu casaco assim. Modifiquei meu casaco. Então agora desenhe uma linha de chamadas em uma linha de gaveta, que então chama em uma linha de gaveta que chama em uma linha de gaveta. Você pega a foto. Esta sequência nunca terminará. Quando eu executar este programa, ele irá criar uma sequência infinita fora de métodos de linha desenhada chamando-se a si mesmos Vamos executar o programa e ver o que acontece. Eu estou lá, você tem isso. A pilha é limitada em tamanho. Se eu tiver muitos métodos chamando todos os métodos, eventualmente a pilha estará completamente cheia na estrutura de rede DOT flui a exceção de estouro pilha . Então, o que aprendemos? That'll Net Framework usa a pilha para rastrear médicos cada vez que você chamar a mensagem todos os seus parâmetros de método. O endereço de retorno em todas as variáveis locais são colocados na pilha. Todo esse bloco de memória é chamado de quadro de pilha. Quando você retorna fora de um método, o quadro da pilha superior é removido. Todas as variáveis locais estão fora de alcance neste momento, e eu destruí. Se você tiver uma sequência infinita off métodos, chamando todos os métodos que o estoque irá encher completamente até que a urtiga lance uma pilha. Exceção de estouro 4. O que é a memória heap?: o outro tipo de memória dentro de um computador é chamado de memória hit ou simplesmente o heap. Vamos dar uma olhada na seguinte linha de código Now. Sempre que as palavras-chave novas aparecem online, você estava criando um objeto no heap. Esta é uma regra fundamental. Objetos da Internet são sempre criados no heap. Eles nunca vão para a pilha. Então eu fiz algumas mudanças no meu programa quadrado de gavetas. Vamos dar uma olhada. Anteriormente, os métodos de linha de gaveta esperavam quatro parâmetros inteiros para desenhar uma linha. Mas agora eu tenho uma gaveta Métodos Polygon, que espera uma matriz fora de linha objetos em desenha tudo de uma só vez. O método de desenho quadrado configura uma matriz de linha com quatro objetos correspondentes aos quatro lados fora do quadrado e, em seguida, chama soltar tudo uma arma para desenhar tudo em um. Vá agora lembre-se, no meu antigo programa quadrado gaveta, eu coloquei pontos de quebra dentro dos métodos de linha de gaveta e quando eu rodei meus códigos, a pilha parecia assim. Mas agora fiz muitas mudanças no programa. Então, como será a pilha na pilha agora? Imagine que coloquei pontos de ruptura dentro da gaveta. Métodos polígonos em executar o meu programa. A pilha no heap seria então parecido com isso. O parâmetro linhas frias existe na pilha porque é um parâmetro, mas eu inicializei com as novas palavras-chave. Portanto, a matriz em si é criada no heap, modo que a variável nesta pilha se refere a um objeto no heap. Você também pode ver que a matriz de linhas tem quatro elementos e que cada elemento se refere a um objeto de linha em outro lugar no Agora, Se você está pensando que tudo isso parece muito mais complicado do que o culto anterior, então você está sob o direito trilha em. Isto leva-me a um ponto importante. Este código, que usa uma adega, é um pouco mais lento do que o meu casaco anterior, que usava inteiros para tudo. E isso ocorre porque todas as referências extras devem ser calculadas, a partir do parâmetro lines na pilha para a matriz. Instância, no heap, mãos de um elemento de matriz do heap para uma instância de objeto de linha em outro lugar no heap. Então, o principal take away, por agora é códigos que usa inteiros é um pouco mais rápido do que o código que usa objetos. Então agora o que acontece quando o método de desenho polígono termina. Esse quadro de pilha é removido nas luzes, parâmetro sai do escopo e é destruído. Mas aqui está algo que você não esperava. Array Z na linha. Objetos no heap continuam a existir agora. Esta é uma situação interessante. O parâmetro dos leões está fora do alcance, mas os objetos no calor ainda estão lá. Dizemos que os objetos são de referência por causa da variável ou parâmetro, que se referem a eles sem escopo. De objetos referenciados continuam a existir e não são destruídos imediatamente. Então aqui está outra tomada importante. O quadro de rede DOT sempre adiará. Limpar os objetos referenciados neste é porque a limpeza do heap leva muito tempo em, adiando-o para o maior tempo possível. Seu código será realmente executado mais rápido, mas eventualmente a estrutura terá que limpar o heap ou ficaríamos sem memória rapidamente . Este processo de limpeza é chamado de coleta de lixo, e acontece periodicamente em segundo plano. Quando a estrutura começa a coleta de lixo. Ele identifica todos os objetos no heap, que não são mais referenciados por qualquer parâmetro variável ou objetos em seu casaco em sua de aloca cada um deles. Voltaremos à coleta de lixo em palestras posteriores, e vou lhe dar algumas dicas sobre como evitar problemas de desempenho em seus códigos devido à coleta de lixo. Então, o que aprendemos? Toda vez que você usou as novas palavras-chave em seus códigos, você está criando um objeto no heap. A variável em si pode viver na pilha, mas se referirá a um objeto no heap. O novo programa de desenho quadrado que usa uma matriz de linha é ligeiramente mais lento do que o programa antigo que usava inteiros em. A razão para isso é que todas as referências de objeto extra precisam ser processadas quando parâmetros e variáveis locais na pilha saem do escopo. Os objetos correspondentes da pilha não são destruídos. Eles continuam a existir em um estado referenciado em D. A próxima estrutura adia a limpeza dos objetos referenciados no heap por tanto tempo quanto possível por motivos de desempenho. Mas, eventualmente, a estrutura iniciará um processo chamado coleta de lixo. Como de aloca todos os objetos referenciados no quadril 5. Quais são tipos de valor?: na palestra anterior, aprendemos sobre esta pilha no calor. O conhecimento que você ganha irá ajudá-lo a avançar quando olhamos para as variáveis, como elas são armazenadas na memória pelo framework dot net e como isso afeta o desempenho do seu código agora. Anteriormente, quando falei sobre essa pilha, mostrei algum código com a gaveta. O Linus. É que usou quatro parâmetros inteiros. Vamos dar uma olhada em um inteiro na estrutura Dark Net. O tipo insurgente faz parte de uma classe especial de tipos chamados tipos de valor, mas qual é o tipo de valor? O tipo de valor é um tipo de variável, onde o tipo do valor fora da variável armazenado juntos. Então, se eu tiver uma variável inteira local com um valor de 12 centenas e 34 esse tipo de entrevista em seu valor será armazenado juntos assim. Então vamos dar uma olhada nessa pilha novamente. Anteriormente, quando eu falei sobre esta pilha, Eu mencionei todos os tipos de dados que o armazenado na pilha eles são parâmetros de mensagem. O endereço de retorno de uma mensagem soa variáveis locais. Então, se eu tiver uma variável local off tipo inteiro com o valor off 12 centenas e 34 ele seria armazenado na pilha como este. Você vê isso? O tipo e o valor armazenados juntos na pilha. Agora tenha isso em mente porque na próxima palestra eu vou falar sobre tipos de referência, que é armazenamento diferente. Então você pode estar se perguntando quais tipos no framework dark net são realmente tipos de valor. Bem, aqui está uma lista completa. Todos os tipos numéricos são tipos de valor, incluindo todos os pontos flutuantes inteiros em tipos decimais. Além disso, booleano em operações e estruturas são tipos de valor. Qualquer outra coisa no quadro dot net é chamado de tipo de referência, que discutirei em breve. Quando as pessoas tentam explicar a diferença entre um tipo de valor e um tipo de referência, muitas vezes você ouve a seguinte explicação. Um tipo de valor é um tipo que existe na pilha. No entanto, este é o valor da sala. Tipos podem existir tanto na pilha no aqui, deixe-me demonstrar. Anteriormente, quando falei sobre o heap, mencionei que tipo de dados são armazenados no heap. Lembra-se do que foi? Era todos os objetos, instâncias criadas com uma nova palavra-chave em c sharp. Então imagine que eu criar um objeto na pilha usando as novas palavras-chave no meu casaco neste objeto tem um enterrado seu campo contendo o valor 12 centenas. 34. Agora este inteiro será armazenado assim. Então você vê, eu sei que tem o tipo de valor no heap para que os tipos de valor podem existir tanto na pilha no dia nos tipos de valor de propriedade definindo não é onde eles são armazenados, mas que o valor é armazenado junto com o tipo. Então deixe-me terminar mostrando a importância de recursos adicionais ou tipos de valor. Digamos que tenho uma mensagem nos meus códigos com duas variáveis A e B. Ambas são apresentadas. A variável A contém o valor 1234 sob a variável B é zero. Agora, o que acontece quando eu atribuí-lo será Confira isso. O valor de A é copiado para ser. Agora esta é uma característica importante dos tipos de valor seus sinais por valor, o que significa que seu valor é copiado. Então, o que acontece se eu comparar A e B? Eles são duas variáveis diferentes que apenas por acaso contêm o mesmo valor. Então, como vai. O quadro dot net interpreta bem a situação assim. O quadro considera essas duas variáveis iguais. Esta é a segunda característica importante fora tipos de valor. Eles são comparados por valor, o que significa que duas variáveis diferentes que possuem o mesmo valor são consideradas iguais. Então, o que aprendemos? Os tipos de valor armazenam seu valor diretamente, juntamente com os tipos de valor de tipo podem existir na pilha em e os tipos de valor de salto são sinais por valor, significa que o valor é copiado sobre o valor. Os tipos são comparados por valor. Duas variáveis com o mesmo valor são consideradas iguais. 6. Quais são tipos de referência?: Na palestra anterior, falei sobre os tipos de valor e mencionei brevemente seu homólogo o tipo de referência. Mas o que é um tipo de referência? Bem, um tipo de referência é um tipo de variável que se refere a um valor armazenado no heap. Não antes. Quando eu falei sobre o heap, Eu mostrei meu programa quadrado desenho modificado que tinha um desenhar um método de polígono, se você se lembrar com um parâmetro de matriz de linha, Então desenhar polígono esperado uma matriz fora de linha objetos. Vamos dar uma olhada nos objetos de linha apenas para atualizar sua memória. Aqui está meu casaco de novo. Você pode ver a definição fora da classe de linha aqui. É um simples contêiner de dados com dois conjuntos de coordenadas. Então imagine que eu tenho uma mensagem com uma variável local fora do tipo de linha. O que seria isso? Um poço de memória, assim. Você pode ver que a variável em si está na pilha, mas ela se refere a uma linha Objetos no mas pode uma variável de tipo de referência também existir no heap? Sim, claro. Tudo o que preciso fazer é criar um objeto no heap. Usando as novas palavras-chave transformou metade que os objetos têm um campo fora da linha do tipo. Agora a memória ficará assim. Você vê que eles agora têm uma variável de tipo de referência no heap, e ele se refere a uma linha objetos, que também é armazenado no que em outro lugar, modo a resumir. Tipos de referência podem existir na pilha no dia no heap, assim como tipos de valor, mas eles sempre se referem a um valor no heap. Deixe-me terminar mostrando a importância de recursos adicionais fora dos tipos de referência. Digamos que tenho uma mensagem nos meus códigos com duas variáveis A e B. Ambas as variáveis são linhas. A variável A refere-se a uma instância de linha na pilha sob a variável B é dito saber. Agora, o que acontece quando eu atribuir um a B. Verifiquem a referência de A é copiada para ser. Este é um recurso importante fora dos tipos de referência. Eles são atribuídos por referência, que significa que a referência é copiada. Você acaba com duas variáveis, referindo-se à mesma instância de objeto no quadril. Então, o que acontece quando comparo A e B? Eles são duas variáveis diferentes que se referem aos mesmos objetos no heap. Como o quadro dot net interpreta esta situação bem assim. O quadro considera essas duas variáveis iguais. Mas espere, sobre este cenário para referenciar variáveis de tipo apontando para dois objetos separados no heap. Mas ambos os objetos continham dados idênticos. Como o quadro dot net interpretará esta situação sons Drinker. A estrutura considera essas duas variáveis como não iguais, então esta é outra característica importante fora dos tipos de referência. Eles são comparados por referência, o que significa que duas variáveis diferentes referentes aos mesmos objetos são consideradas iguais. Mas duas variáveis diferentes, referindo-se a dois objetos separados, mas idênticos, são consideradas não iguais. Então, o que aprendemos? Os tempos de referência podem ser detectados para o armazenamento de tipos de referência sem valor, uma referência ao seu valor, e esse valor é sempre armazenado na referência. Tipos podem existir nesta pilha no dia no, mas seu valor é sempre armazenado na referência. Tipos são atribuídos por referência, significa que a referência é copiada sobre referência. Os tipos são comparados por referência. Duas variáveis referentes aos mesmos objetos são consideradas iguais, e duas variáveis referentes a objetos separados, mas idênticos, não são consideradas iguais 7. O que é boxe e unboxing?: Nesta palestra, vou mostrar-vos um mistério. Dê uma olhada neste código. Este é um programa muito simples. Comecei com a variável A contendo o valor 1234. Então eu declaro uma segunda variável. Esteja fora do tipo objetos. A Andi. Atribuí “A “para “B “em C. Sharp. Todos os tipos herdam de objetos, incluindo inteiros, para que você possa colocar basicamente tudo em uma variável de tipo de objeto. Mas esperar em jurados são tipos de valor, e objetos são tipos referenciados. Então, na memória, minhas variáveis são armazenadas assim. Aqui está a minha variável inteira A com o seu valor off 1234. E aqui está minha variável de objeto B. Mas B é um tipo de referência, e nós aprendemos na palestra anterior que tipos de referência sempre se referem a um valor no heap aqui. Isso não é possível porque A é uma variável local, então ela existe na pilha encontrada é um tipo de valor, então seu valor também é iniciar no estoque. Simplesmente não há maneira de B se referir a um porque ambas as variáveis recita em diferentes tipos de memória. Um tipo de referência nunca pode se referir à memória de pilha, portanto, este programa nunca pode funcionar. Certo? Bem, este tem. Volto a Dezembro no estúdio Andi, dirijo o programa. Aqui vamos nós. Legal. Na verdade, funciona. Mas como isso é possível? Agora, isso é estranho. Com base no que aprendemos em palestras anteriores, este programa não deve funcionar. Mas ainda assim faz. Como é que isso é possível descobrir? Deixe-me de obrigar este programa a uma linguagem intermediária. Examinando as instruções de linguagem intermediária, podemos encontrar uma pista. E aqui está. Olha para isto. Instrução de linguagem intermediária chamada livros. Consegue adivinhar o que faz aqui? Vou desenhar no quadro negro. Aqui estão os layouts de memória com as duas variáveis A e B. Agora a instrução livros faz isso. Então, para fazer o programa funcionar, a estrutura Net realmente copia o valor do entrevistador da pilha, em seguida, tributar o valor em objetos nele Coloca esses objetos no para que a variável B possa então referir-se a ele. Todo esse processo é chamado de boxe. boxe acontece sempre nos bastidores. Quando você tem um parâmetro variável campos ou propriedade off time objetos e você atribuir um tipo de valor para seu boxe é bom porque ele tipo de desfoca a linha entre tempos de valor e tipos de referência. Mas o boxe também pode ser uma dor porque ele introduz despesas extras em seu código. Agora você pode estar se perguntando se há um processo correspondente chamado unboxing. Sim, há. Aqui está o meu programa de novo. Vamos dar uma olhada na linha final. Eu declaro uma variável, ver off type integer e atribuir o valor do objeto a sua usando um typecast. Mais um pouco de magia. Na verdade, porque ver, existe na pilha e ser os objetos refere-se a um objeto no na linguagem intermediária . A instrução correspondente está aqui. Chama-se “Unbox”. Deixe-me voltar ao quadro em “Desenhe o Processo de Unboxing”. Começamos a partir da situação da caixa com o inteiro no Agora. Então isso acontece. Unboxing descompacta o inteiro no heap e copia o valor de volta para a variável Ver na pilha. desboxe acontece sempre nos bastidores quando você tem um valor de objeto e o converte um tipo de valor. Boxe e desboxe podem afetar seriamente o desempenho de seu casaco, então certifique-se de evitá-lo tanto quanto possível em seções críticas de missão. Vou compartilhar algumas dicas e truques em palestras posteriores sobre como fazer isso. O que aprendemos? Boxe é o processo de tirar tipos de valor na pilha, embalando-os em objetos como colocar esses objetos no heap. boxe acontece sempre que você atribui o tipo de valor a um parâmetro variável de campo ou propriedade fora de objetos do tipo. Unboxing é o processo inverso. Objetos fora do heap são descompactados no valor. Tipos dentro são copiados de volta para a pilha. Aprenda que o boxe acontece sempre que você não tem valor de objeto e você joga um tipo de valor. boxe e o desboxe afetaram negativamente o desempenho de seu casaco e devem ser evitados em seções críticas de missão. 8. O que são cadeias imutáveis?: nesta palestra. Vamos dar uma olhada na classe string em dot net agora. O que você acha que é uma string de tipo de valor ou um tipo de referência? Vamos descobrir. Escrevi através de códigos para testar o que é realmente uma string. Confira isso. Eu começa declarando variável String a em inicializar É para o valor ABC. Então eu declaro uma segunda variável de cadeia B e um sinal A para B. Na próxima linha, Eu adiciono um único caractere para o final do fluxo ser e, finalmente, eu escrever ambas as cordas para o console. Agora, se cadeias de caracteres são tipos referenciados, a pilha e heap ficaria assim. Eu teria as variáveis A e B, ambos apontando para os mesmos objetos string no heap. Então, se eu modificar a variável de string B, a string invariável A também seria modificada porque ambas as variáveis se referem à mesma string. No entanto, se cadeias de caracteres são tipos de valor, o segundo heap seria parecido com este. Eu teria que variáveis a e B contendo strings separadas quando eu modifico string variável B. A outra string invariável A não é afetada. Qual é o cenário certo. Acha que vamos descobrir rodando meu código? Aqui vamos nós e você tem isso. Strings são obviamente tipos de valor, certo? Bem, nenhuma string é realmente tipos referenciados na memória. As coisas parecem assim. Aqui estão as duas variáveis A e B, ambas referindo-se à mesma string no heap. Mas algo especial acontece quando eu modifico a variável de string B em vez de modificar a string no heap diretamente. Isso acontece. Dizemos que as cordas são imutáveis. Objetos na Rede. Qualquer modificação de uma string resulta em uma nova string sendo criada no fluxo original é deixada intocada. Devido a isso, strings se comportam como se fossem tipos de valor. Então, quais são os benefícios de ter cordas imutáveis ameaça segurança, cordas imutáveis? Uma ameaça segura? Porque eles não podem ser modificados economia de memória. As cadeias de caracteres idênticas podem ser unidas com segurança. Isso é chamado em transformar a primeira atribuição para copiar uma string. Tudo o que você precisa fazer é copiar a referência em vez de ter que copiar todos os caracteres sobre um por um. E as primeiras cadeias de estagiários de comparação podem ser comparadas comparando a referência em vez de ter que comparar todos os caracteres um por um. Então vamos dar uma olhada nos códigos intermediários para ver o que está acontecendo nos bastidores. Eu coloco um ponto de ruptura na linha final do meu programa. Inicie o programa e, em seguida, mude para códigos intermediários. A primeira linha está aqui. Uma string com conteúdo. ABC é carregado no usando a instrução string de carga em lojas invariáveis A com uma instrução de localização de loja . Zen A é atribuído a ser simplesmente carregando a referência invariável A com um local de carga em armazená-lo para variável B com um local de loja, uma string super rápida atribuições por referência exatamente o que esperaríamos para um tipo imutável . A magia acontece quando modifico a corda invariável estar nos bastidores. O framework chama um método concussed string. Deixe-me procurar esse método usando o navegador de montagem no estúdio de verão. Aqui está. Você pode ver que a bagunça do gato cria uma nova corda aqui. Em seguida, copia ambos os argumentos de string para a nova string e retorna uma referência para a nova string aqui. Então aí está. Em vez disso, ao modificar esta string invariável ser os códigos cria um fluxo inteiramente novo, copia tudo sobre o seu e armazena uma referência para a nova string. Invariável Seja a cadeia de caracteres antiga em B é deixada no heap em um estado de acordo referenciado aguardando para ser coletado lixo. Todos os métodos na classe string que modifica a string de qualquer maneira tem esse comportamento. Em vez disso, modificar diretamente o fluxo. Ele cria uma nova string inteira e coloca as modificações. Lá dentro. A string original é deixada intocada. Então, o que aprendemos? Strings são tipos referenciados em e imutáveis. Devido a isso, strings se comportam como se fossem tipos de valor. São sinais e compara por valor. Cordas imutáveis são ameaças seguras em cordas musicais são rápidas porque podem ser assinadas e compara por referência. Strings imutáveis salvar memória porque cadeias idênticas podem ser mescladas na memória. 9. Curso de colisão em linguagem intermediária: nesta palestra, eu vou falar sobre linguagem intermediária. Então, o que exatamente é linguagem intermediária? Bem, deixe-me começar mostrando um compilador clássico, neste caso um C ou C mais, mais compilador. No topo. Fora dos quadros negros, você vê um simples pedaço de código, um loop de quatro que adiciona um inteiro a um resultado frio variável. A, C ou C mais compilador levaria esses fragmentos de revestimento fonte e compilá-lo para a linguagem de máquina, que é basicamente apenas um monte de números armazenando memória em executado pela CPU em seu computador, mas um darknet compilador. Poeira as coisas diferente. Vamos dar uma olhada no mesmo pedaço de códigos escritos em C. Sharp. Um compilador C sharp irá primeiro compilar a costa fonte para uma linguagem intermediária especial chamada Bem, você adivinhou esta linguagem intermediária comum. C i, l ou seda. O mesmo revestimento é armazenado em um arquivo dll ou xer. Quando o código é executado, outro compilador entra em ação. Este compilador é chamado de compilador jit, ou G i T G. I T representa apenas no tempo. O compilador é executado apenas no tempo nos últimos momentos possíveis antes que os códigos precisam estar errados. O compilador Jit leva a linguagem intermediária e compila a linguagem da máquina. Então, por que esse complicado processo de compilação de dois passos? Bem, porque na verdade tem uma série de vantagens importantes. Primeiro, o casaco de seda pode ser otimizado para a plataforma em que está sendo executado. Então diferenças entre, por exemplo, um M D na Intel. uso da CPU pode ser totalmente explorado pelo compilador jit para criar linguagem de máquina rápida e otimizada . Segundos. O frio logo será totalmente portátil, pois não está vinculado a uma plataforma de hardware específica. O casaco pode ser executado no Windows em Lenox em computadores Apple. Na verdade, eu criei todo esse curso em um Apple MacBook Pro e todos os exemplos de código. Você vai ver uma execução nativa no OS X, mas você poderia pegar esses mesmos bols executados que eu estou executando, copiá-los para inclinadores ou janelas e executá-los lá. Isso funcionaria. Outra vantagem útil fora casaco de seda é que ele pode ser muito luta antes de correr para se certificar de que o casaco não executa quaisquer operações perigosas e, finalmente, ainda frio pode ser anotado com metadados, por exemplo, instruções de serialização dizendo frio externo exatamente como converter suas classes toe XML e volta. Há alguma desvantagem em usar casaco de seda? Bem, sim. Usar uma copulação de dois passos é um pouco menos eficiente do que compilar diretamente de códigos-fonte para linguagem de máquina. Tão compilado. Os resfriados Net é ligeiramente mais lento do que compilado diretamente C ou C mais butts código. Compiladores o causaram muito bom nos últimos dois anos, e assim essa diferença tornou-se microscopicamente pequena. Hoje é quase impossível de mão linguagem máquina cult que é mais eficiente do que o compilador C Sharp produz. Então vamos olhar para a linguagem intermediária com mais detalhes. Como é que a linguagem realmente funciona? A linguagem intermediária é baseada em três concertos. A primeira é a sequência de instruções mostradas nas gravações no quadro negro. Programas de linguagem intermediária contêm, ou as instruções que são executadas em sequência, assim como um programa nítido em C. As variáveis locais em um programa de linguagem intermediária, um armazenado em slots especiais chamados locais. Há vários locais disponíveis. Desenhei quatro no quadro à esquerda. Finalmente, há uma avaliação Pilha uma pilha é uma coleção off valores com duas operações básicas, um push chamado de Load in Ill, que adiciona valor à pilha e empurra tudo o resto para baixo por um nível, o outro é um pop chamado Store in ill, que remove o valor superior da pilha em movimentos completos. Outros valores sobem um nível. Existem três grupos fora de instruções em instruções de linguagem intermediária que empurrou dados sobre a avaliação. Instruções presas que executam operações na pilha em instruções que pop valores da pilha. Então vamos dar uma olhada. É um programa muito simples. Dê uma olhada nas duas linhas de código a seguir inicializei um inteiro com o valor 456 e, em seguida, adiciona um a esse valor. Como seria este programa em linguagem intermediária? Bem, este programa era composto de apenas quatro instruções de linguagem intermediária. A primeira instrução é cargas constantes. Um, que carrega o valor inteiro assinado de quatro bytes. Um na avaliação preso nota que há apenas uma variável local no meu programa chamado I. E assim ele fica armazenado na localização zero, com o valor inicial off 456. A próxima instrução é cargas Localização zero, que carrega o valor fora da variável I no local zero na avaliação preso. Então agora temos dois números do estoque um e 456. A terceira instrução é Tia, que adiciona os dois primeiros números na pilha juntos. Assim, o resultado é 457 que vai para a pilha e substitui os dois números originais. Então agora temos um único número na pilha. 457. O início final é a loja Localização zero, que exibe o valor superior fora do estoque de avaliação e o armazena em Localização zero, que corresponde à variável I. Aqui estão algumas outras instruções que você pode encontrar quando Você está olhando para casaco cortante C compilado. Os livros e instruções de descaixa fazem exatamente o que você pode esperar. Eles caixa e, em seguida, livros tipos de valor na instrução B e E significa ramo, não igual. Ele salta para um casaco Localização diferença. Se os dois primeiros números no estoque de avaliação não forem iguais, legais e chamados virtuais, chame estático em membros de classe não estáticos, elementos de carga e elementos de armazenamento carregar e armazenar elementos em uma corrida dimensional. Nova matriz como novos objetos cria uma nova matriz, respectivamente. Os novos objetos no retorno retorna de um métodos e lance lança uma exceção. Então você pode ter pensado que a linguagem intermediária é muito complexa, mas na verdade é bem simples. A linguagem não é tão difícil de entender, e nas próximas palestras estavam indo para dar uma olhada em compilar, ver lotes de odor tubarão e analisar como o compilador traduz ela código fonte afiada em linguagem intermediária sobre o que o implicações de desempenho são. Vamos dar uma olhada no programa simples. Dê uma olhada no meu código aqui. Eu começo com o número dois e, em seguida, uso este loop quatro para calcular o 1º 16 potências de dois multiplicando repetidamente o número por dois. Então, como isso parece em linguagem intermediária? Bem, tudo o que eu tenho que fazer é definir um ponto de interrupção no final do programa aqui, executar o meu programa e então mudar para a vista de desmontagem como esta. Estamos agora olhando para o revestimento linguagem intermediária compila anotado com linhas do código-fonte original para que pudéssemos facilmente encontrar os potros compilados para uma determinada linha fora C códigos afiados. Então vamos começar com a declaração fora da variável número. Você vê uma constante de carga para a instrução aqui, que empurra o número dois na pilha e, em seguida, um local de loja zero, que armazena o número no local zero. Então sabemos que a localização zero corresponde à variável numérica. Então vem o fórum. Agora preste muita atenção às etiquetas de endereço aqui porque os códigos são mostrados fora de ordem para torná-lo mais facilmente legível. O primeiro passo é inicializar a variável I 20 Então temos um zero constante de carga e armazenar instruções de localização um aqui. Então agora sabemos que a variável I é armazenada na localização um. Então o culto salta para o local 14. Lá temos um local de carga um, que é o muito bem. Eu carrego Constant 16 que carrega o número 16 em, em seguida, uma instrução BLT. Agora BLT significa filial menos do que on. Ele vai saltar para a localização A. Se a variável I é menor que 16. localização A contém o corpo principal fora do loop. Ele carrega a variável número na pilha. Em seguida, ele carrega o número dois na pilha. Ele multiplica ambos os números na pilha juntos e armazena os resultados de volta na variável número porque o código está fora de ordem. A próxima instrução após a localização F está aqui na Localização 10. Estamos de volta onde a variável I é ID incremento foi testado. Se estiver abaixo de 16 foi a variável. Eu alcanço o valor 16. Nós vamos completar através da instrução BLT aqui. O próximo local após o local 17 é um. Veja na instrução de retorno retorna fora do método, e lá você tem isso. Um programa de cinco linhas C afiado compila apenas 17 instruções de linguagem intermediária sobre o frio compilado foi bastante fácil de ler, então parabéns você agora é capaz de ler compilado. Ver casaco de tubarão. Ser capaz de ler e interpretar códigos linguísticos intermediários é uma habilidade muito importante, que irá ajudá-lo muito quando você está otimizando o desempenho de seu casaco. Então, o que aprendemos? C. Sharp Coats compila para o Linguage Coat intermediário, que é então compilado novamente por um compilador just in time. Dois linguagem de máquina Gits Copulation pode otimizar códigos para hardware local em códigos de idioma da mídia , é portátil e pode ser executado em várias plataformas como Windows, Linux e Mac. Pode ser muito lutar pela correção antes de correr, e o treinador pode ser anotado com metadados extras, por exemplo, para orientar a serialização. Intermediate Language usa locais para armazenar variáveis locais e usa uma pilha de avaliação para executar operações em dados. Intermediate Language tem construído em suporte para a criação de objetos chamando métodos no acesso a campos. Linguagem intermediária construiu suporte para criar e manipular um aumento dimensional . 10. Dica nº 1: evitar o boxe e unboxing: anteriormente, eu lhe disse que o boxe e o desboxe afetam negativamente o desempenho fora do seu código, e você deve evitá-lo sempre que possível, especialmente em seções de missão crítica. Mas quão ruim é a sobrecarga de desempenho fora do boxe e unboxing? É pior a preocupação? Vamos descobrir que eu escrevi um programa para medir a diferença de desempenho entre método que usa apenas inter jobs versus e métodos que faz exatamente a mesma coisa usando uma variável de objeto . Aqui está o código. Tenho dois métodos. Meça a medida de um homem B. O 1º 1 medido a leva um inteiro e adiciona o valor um a ele um 1.000.000 vezes. Eu uso um dedo de classe cronômetro medir com precisão o número fora de milissegundos que leva para executar o loop. A segunda mensagem Medida B, faz exatamente a mesma coisa, mas agora usa uma variável de tipo de objeto em vez de um inteiro. Tudo o resto é igual. Um milhão de repetições estou adicionando o número um em cada loop. É oração. Se eu rolar para baixo até os métodos principais, você vê que eu começo chamando ambos os métodos de medição e descartando os resultados. Eu faço isso porque pode haver um início de atraso no meu casaco que poderia distorcer os resultados da medição. Então, para eliminar esse atraso, eu faço o teste completo. Uma vez descoberto os resultados em, em seguida, execute o teste novamente. Finalmente, eu exibo os resultados em milissegundos no console para ambos os métodos. Presumo que o método A seja mais rápido. Então eu também exibo quantas vezes o Método B é mais lento que o método A. Não, eu já te disse boxe e um boxe introduz um desempenho significativo. Overheads em seu resfriado. Então vamos descobrir de quanta sobrecarga estou falando. Deixe-me executar o programa agora. Aí está você. A mensagem A, que usa uma variável inteira, leva 10 microssegundos. O método B, que usa uma variável de objeto, leva 55 microssegundos. método B é 5,5 vezes mais lento do que o método A. Vamos dar uma olhada no código intermediário. Vou começar com a adição de inteiro no Método A. Se eu disse dois pontos de quebra e executar em, em seguida, mudar para a vista de desmontagem. Aqui vamos nós. Você vê que o código intermediário é anotado com código fonte C nítido, então é realmente fácil localizar a linha que queremos. Neste caso. A adição no Método A. Here is Now, antes de explicar o que as instruções do fazem, tenha em mente que os códigos intermediários usa uma pilha de avaliação especial para realizar cálculos. Números e variáveis são carregados nesta pilha usando instruções de carga, começando com LD em resultados são armazenados de volta em variáveis com instruções de loja começando com S t. A adição no meu código vai ser uma sequência off load em na loja em revestimento intermediário . Então aqui estão as quatro instruções. O primeiro 1 local de carga. Um carrega a primeira variável local, que passa a ser a variável A na pilha de avaliação. Em seguida, vem a constante de carga uma instrução, que carrega o número um até a pilha de avaliação. A instrução add adiciona os dois principais números na pilha juntos, neste caso, o valor de um abaixo do número um. E substitui esses dois números com os resultados da adição. Finalmente, a localização da loja, uma loja de instruções. A adição resulta na variável A. Agora vamos olhar para a mesma linha na mensagem. Esteja usando a variável de objeto. Aqui está outra vez. Começamos com a localização de carga um, que carrega a variável de objeto A até a pilha de avaliação. Agora tenha em mente que uma variável de objeto é um tipo de referência, então agora temos uma referência a um objeto no heap no estoque de avaliação. Assim, a próxima instrução tem que estar na instrução unbox porque os objetos no calor precisa ser descompactado no valor dentro precisa ser copiado até que a pilha de avaliação. Em seguida, vem a constante de carga familiar, que carrega o número um e, em seguida, a instrução add para adicionar os dois valores juntos. Nenhum a é uma variável de objeto, portanto, os resultados da adição precisam ser embalados em um objeto e colocados no heap . Então a próxima instrução tem que ser caixa, que faz exatamente isso em lugares, uma referência a esses novos objetos no heap na avaliação preso. Finalmente, temos a localização da loja um que armazena o novo objeto referência dia invariável. Então método um método ser contido praticamente o mesmo revestimento, uma constante de carga de baixa localização, em seguida, um anúncio e, finalmente, um local de loja. Mas o método B precisa de uma unbox adicional na instrução caixa porque estamos realizando em enterrado. Sua adição no tipo de referência de capaz na estrutura de rede DOT precisa mover dados entre essa pilha e o heap para fazer isso funcionar. Portanto, a diferença no desempenho é inteiramente devido a estas duas caixas de instruções no Unbox. Evitar boxe em unboxing em seu próprio código é fácil. Simplesmente evite o tipo de objeto. Mas você sabia que o framework Net está cheio de classes que usam objetos ou objetos? Arrays para armazenamento interno, por exemplo, praticamente todo o sistema faz. O espaço de nomes de coleções usa objeto uma corrida internamente, portanto, mantenha-o longe das seguintes classes no código Mission Critical. Outra classe popular é a tabela de dados em tabelas de dados de ponto do sistema armazenar o valor cada linha em uma matriz de objetos. Mesmo tipos tabelas de dados não são seguros de usar porque eles usaram tipos de moldes em cima dessa mesma matriz de objetos. Então, para manter seu código rápido, considere o uso de classes de coleção genéricas no sistema. As coleções não têm espaço de nomes genéricos? Ou se você souber o número off elementos com antecedência, considere usar uma matriz de tipos unidimensionais simples para tabelas de dados. Não há uma alternativa simples. Se você pudesse fugir com ele, re fator seus códigos para usar um leitor de dados, porque eles são muito mais rápidos do que uma tabela de dados. Mas lembre-se, uma tabela de dados é o resultado de uma operação de banco de dados, que pode levar muitos, muitos milissegundos para ser concluída. Não há muitos pontos para otimizar a recuperação de dados que vem depois. A menos que você estivesse fazendo milhões de operações no mesmo dia, a mesa se opõe. Então, o que aprendemos? Casting objetos, Variáveis para tipos de valor? Introduz na instrução unbox em intermediários revestimento armazenar tipos de valor em variáveis de objeto introduz uma instrução caixa em códigos intermediários. Códigos com livros em livros é executado até cinco vezes mais lento do que o mesmo código sem o é para instruções, você deve evitar a transmissão de e para um objeto no código de missão crítica. Você deve evitar o uso de classes de coleta não genéricas em Mission Critical Code em. E você deve evitar o uso de tabelas de dados em códigos de missão crítica, mas somente se você executar muitas operações na mesma tabela de dados. Objeto 11. Dica #2: adicione cadeias de caracteres de forma eficiente: nesta palestra, eu quero dar uma olhada mais de perto na concatenação de strings ou como adicionar strings. Juntos, há duas maneiras de adicionar cordas juntas. O primeiro é aquele que todos usamos corretamente em todos os lugares em nossos códigos. Nós aprendemos a encadear variáveis em conjunto usando o operador plus. Vamos dar uma olhada em algum código. Fiz uma aplicação para medir o desempenho adicionando cordas juntas. Começo com uma string vazia aqui e, em seguida, neste loop, adiciono um único caractere à string 10.000 vezes. Não, se eu executar o programa, você pode ver o desempenho aqui na janela de saída em milissegundos. Mas há outra maneira de adicionar cordas. A estrutura de massa Net fornece uma classe especial chamada construtor de cordas para cordas de construção de poço . Se eu escrever o mesmo casaco usando um construtor de cordas, começando com uma string vazia, adicionando um único caractere 10.000 vezes o código parece com isso praticamente o mesmo excesso I usando os métodos canetas em vez do operador plus. Mas o efeito é o mesmo na adição de cordas. Não, deixe-me incomum os códigos de medição para este segundo método em e executar o programa novamente para que possamos comparar os resultados. Chicken faz concatenação de string usando o operador plus leva 242 milissegundos, mas os mesmos códigos usando um construtor de cordas leva apenas um milissegundos. O construtor de cordas é 242 vezes mais rápido. Agora eu tenho que fazer uma pequena declaração de responsabilidade. Quando você repetir este experimento em seu próprio computador, você verá tempos mais rápidos, provavelmente na faixa de centenas e 50 a 200 milissegundos. A razão para o meu desempenho mais lento é que eu estou executando um programa de gravação de tela em segundo plano agora em que está consumindo ciclos de CPU na alocação de memória disponível. Lembra da palestra sobre cordas imutáveis? Mostrei que a classe string é uma classe imutável, que significa que qualquer modificação na string cria uma nova string. Isso faz com que a classe string se comporte como um tipo de valor, mesmo que seja realmente um tipo de referência. Então, o que está acontecendo na memória durante o Luke quando eu estou adicionando à corda? Aqui nos quadros negros estão as 3 primeiras iterações fora do loop. Você pode ver que este é um processo super ineficiente. Toda vez que eu tia, um personagem para a corda. Toda a cadeia de caracteres é copiada para uma nova cadeia de caracteres no heap. Meu casaco está fazendo 10 milhares de cópias de memória nos bastidores. Outra grande desvantagem fora deste casaco é que ele deixa um rastro fora de objetos de string referenciados no heap para 10.000 iterações. O código deixa 9999 objetos de string mortos atrás. O coletor de lixo vai ter muito trabalho limpando tudo isso agora. Compare isso com um construtor de cordas. Um construtor de strings usa uma matriz de caracteres de um determinado comprimento padrão e simplesmente escreve texto na matriz. Então as 3 primeiras iterações pareciam assim muito melhor, não acha? Cada adição de string escreve um caractere no seu A. Isso é muito mais eficiente, e quando terminamos com o fluxo, nós simplesmente de referenciado O construtor de cordas no coletor de lixo só precisa limpar um objeto único, então aqui está o principal tirar desta palestra. Se você estiver adicionando strings em seu código, use sempre um construtor de strings. Tente evitar a adição de strings usando o operador mais. Eu não faço minhas medidas anteriores realizando 10.000 adições. Mas como é que este construtor de cordas se acumula quando eu só faço uma ou duas adições? Vamos descobrir pelo que ele encontra os meus códigos assim. Tenho dois loops agora. A primeira olhada só faz um número limitado. Edições de cordas 2 a 19. O loop externo repete este experimento 10.000 vezes. Andi acrescenta o tempo total. Em milissegundos, I gera o número de adições 2 a 19 e para cada adição conta. Eu exibo o número total fora de milissegundos para strings regulares no para o construtor de cordas. Deixe-me executar o programa. Aqui vamos nós lá para até quatro adições. As cordas regulares são realmente mais rápidas do que os construtores de cordas acima de quatro adições. O construtor de cordas é mais rápido. Aqui está a saída do programa, plotado como um gráfico de linhas. A linha azul no gráfico é o desempenho de edições regulares de string. Você pode ver que strings irregulares superam os construtores de cordas até quatro edições. São cinco ou mais adições. String construtor classe torna-se mais eficiente em quanto mais adições você faz, Quanto maior o desempenho construtores cadeia leva se torna agora. A razão para esse comportamento é que a classe construtor de fluxo tem alguma sobrecarga na configuração de seu terreno de caráter interno, mantendo o controle fora das asas de corda em. E é capacidade interna de buffer ao expandir esse buffer quando necessário. Os cones que usa cadeias de caracteres regulares pouco antes de uma sequência fora de operações de cópia de memória para implementar as adições. Estes são realmente mais eficientes do que o construtor de cordas, mas apenas até um ponto, e esse ponto é exatamente quatro edições. Então, como você deve fazer concatenação de string? Bem, se você tem quatro adições ou menos, não há nada de errado em usar variáveis de string regulares no operador plus. Isso realmente lhe dará o melhor desempenho se você estiver indo para adicionar mais de quatro strings juntos ou você não sabe o número off adições com antecedência usado para string Builder Em vez disso, uma vez que você obter até milhares de adições ou, neste caso, 10 milhares de edições, exatamente este construtor de cordas é realmente mais de 240 vezes mais rápido do que as adições de string regulares . Então, nesse cenário, sempre use o construtor de cordas 12. Dica 3: use o curso correto de lista: se quisermos armazenar uma coleção fora de valores. Existem muitas possibilidades no framework dot net. Temos as classes de coleção nas coleções de pontos do sistema. Espaço de nome. Temos coleções genéricas no sistema. Faz coleções. Faz espaço de nome genérico Onda. Temos tipos regulares de uma corrida. Então, qual é a abordagem mais rápida? Vamos descobrir. Deixe-me trocar para o meu casaco. Vou começar por medir o desempenho muitas vezes lista de matriz. Eu tenho um loop aqui que adiciona um milhão de inteiros para a lista de arrays. Aqui estão os segundos perdidos que também acrescentam um milhão de insurgentes. Mas aqui ele usa lista genérica off tipo inteiro para armazenar os valores. Vamos rodar os códigos para comparar o desempenho. Aqui vamos nós os resultados. Longe, os códigos de lista de matriz leva 254 milissegundos, enquanto a lista genérica leva apenas 42 milissegundos. A lista genérica é seis vezes mais rápida. Para entender o motivo disso, vamos dar uma olhada nos layouts de memória para Ray List. Depois de três adições, a pilha e a pilha pareciam assim. Agora, se você se lembra da palestra sobre boxe e unboxing, eu mencionei que muitas coleções no sistema de pontos coleções de nome espaço usam objetos, arrays para armazenamento interno. A lista de matriz não é exceção, portanto, cada elemento na lista de matriz é uma referência a um inteiro in a box, que está em outro lugar no heap. Em Nós vimos que boxe e Unboxing introduz um desempenho significativo. Custos gerais para o seu casaco. Agora compare isso com o layout de memória de uma lista genérica. Mais uma vez, a lista está no calor, mas agora os inter jurados também são armazenados diretamente dentro dos elementos da lista. E isso ocorre porque uma lista genérica fora do tipo inteiro usa uma matriz inteira nativa para armazenamento interno em não uma matriz de objeto. Isso elimina todos os objetos. Referências dos elementos da lista em também remove a necessidade de boxe. Assim, o principal take away é sempre usar listas genéricas e classes de coleção em Evite as classes no espaço de nomes de coleções de pontos do sistema para códigos de missão crítica. Agora vamos tentar um terno Alternativo. Um array inteiro nativo pré-inicializado para um milhão de elementos. I loop sobre cada elemento da matriz e definir o valor um por um. Deixe-me comparar este casaco com os outros dois. Aqui vamos nós agora. Isto é muito mais rápido. A matriz nativa leva apenas 11 milissegundos para ser executada. Enquanto a lista genética precisa de 52 milissegundos, a matriz nativa é quase cinco vezes mais rápida do que a lista genérica. A razão para este excelente desempenho é que a linguagem intermediária que o compilador C sharp compila, também, tem suporte nativo para uma corrida. Vamos dar uma olhada nas minhas compilações frias. Vou definir um ponto de interrupção, a executar o programa mudar para a vista de desmontagem. Aqui vamos nós, e aqui está a missão. Como você pode ver, a referência à matriz é carregada primeiro na pilha de avaliação. Em seguida, o array desloca para o elemento que queremos gravar é carregado na pilha em . Finalmente, o valor que deve ser definido para os elementos é adicionado à pilha. E, em seguida, vem uma instrução Set Elements, que grava o valor nos elementos de matriz especificados. Então temos uma instrução dedicada chamada elementos de conjunto para escrever valores em uma corrida. Nenhum dos códigos usando uma matriz é rápido. Agora. Você pode estar afundando que eu estou trapaceando. Porque a lista de matriz e as classes de lista genérica usadas na matriz interna em continuamente redimensionar esta matriz quando ela transborda, enquanto minha matriz entrevistador já foi pré-inicializada para um milhão de elementos. Então nunca transborda. Então deixe-me modificar meus códigos para remover essa sobrecarga. Vou para a linha aqui onde a lista de array é inicializada. Coloquei uma capacidade padrão de um milhão de elementos. Então eu vou para esta linha em. Eu faço o mesmo para a lista genérica. Agora deixe-me executar o código de novo. Veja, a lista genérica agora leva apenas 31 milissegundos para ser executada, o que é 40% mais rápido do que a execução anterior. A lista de arrays leva 187 milissegundos, o que é uma melhoria de apenas 26%. A matriz nativa leva 12 minutos. Segundos em seguida ainda é a opção mais rápida, 2,5 vezes mais rápida do que a lista genérica. O suporte embutido para Boinas na linguagem intermediária garante que eles sempre superarão as aulas de coleta. A lista genérica e a lista de matriz simplesmente não podem competir com essa função Desempenho. Aqui está um gráfico Com todos os resultados de desempenho, a lista de arrays gasta a maior parte de seu tempo no boxe enterrado seus dados pré dimensionar o mais frágil através do número correto de elementos aumenta o desempenho em 40%, mas ainda não é muito bons resultados. A lista genérica é muito mais rápida porque pode armazenar a introdução diretamente nos elementos de lista no heap sem precisar de uma referência de caixa extra e pré-dimensionar a lista para o número correto. Elementos desligados aumentam o desempenho em 26%. O array nativo continua sendo a opção mais rápida. Mesmo com listas precisas, o array ainda é 2,5 vezes mais rápido que a lista genética. Então, quando você deve usar uma corrida? Bem, se você tem códigos de missão crítica sobre o número off elementos é saber com antecedência usando matriz. Se você tem códigos de missão crítica sobre o número off elementos não é conhecido com antecedência, use uma lista genérica. Evite as classes em coleções de pontos do sistema. Eles usam boxe em nosso substituído pelas classes genéricas em coleções DOT sistema não genérico 13. Dica #4: use o tipo de matriz correta: Na palestra anterior, analisamos coleções genéricas e não genéricas e as comparamos com o aumento nativo. Descobriu-se que os arrays nativos são os mais rápidos, mas coleções genéricas têm a vantagem de que eles crescem automaticamente que você Portmore elementos neles. Então, se o número off elementos é conhecido com antecedência e array é a opção mais rápida, mas o framework dot net realmente suporta três tipos de uma corrida. O primeiro tipo é a matriz unidimensional que vimos na palestra anterior. Qualquer coisa declarada com os colchetes de raios quadrados em C Sharp é uma matriz unidimensional, por exemplo, a matriz entrevistadora mostrada aqui. Você declara a matriz assim, Andi, você acessa elementos como este. O segundo tipo é uma disposição multidimensional. Por exemplo, uma matriz bidimensional é declarada assim, e você acessa elementos como este. O terceiro tipo é uma matriz de jaqueta. Isto é simplesmente uma matriz fora de uma corrida. Você declara uma jaqueta, uma boina como esta e, em seguida, cria uma nova instância de matriz para cada elemento de nível superior como este , então você acessa e elementos como este. É chamado de uma matriz de jaqueta, porque cada elemento de nível superior pode ter um número de diferença fora de elementos de sub-nível. Um conjunto de dois por dois pode ter um macaco lado direito como este. Então, como essas matrizes se comparam? Bem, vamos ao estúdio Xamarin e descobrir que escrevi um programa que inicializa é três. Aumente cada um contendo um milhão de elementos. Aqui está a matriz unidimensional, com 1000 vezes 1000 elementos neste loop sinais de valor para cada elemento. E aqui está a matriz bidimensional, agora inicializada para milhares por 1000 elementos. Eu tenho dois loops aninhados para percorrer cada linha e coluna, e eu atribuo valor a cada elemento de matriz. A terceira mensagem usa matriz jaquetas. A desvantagem de matrizes jaqueta é que você tem que inicializar toda a matriz externa com instâncias de matriz. Esse código está aqui. Vamos descobrir qual array é o mais rápido. Pronta. Aqui vamos nós. Isto provavelmente não foi uma surpresa. A matriz unidimensional é a mais rápida, com 12 milissegundos. Em seguida, vem a matriz de jaqueta com 16 milissegundos e, finalmente, a matriz bidimensional com 24 milissegundos. Agora você provavelmente vai se lembrar da palestra anterior que a linguagem intermediária tem suporte nativo para apagar unidimensional. É por isso que a matriz unidimensional é mais rápida na matriz irregular vem em segundo. Uma matriz irregular é simplesmente uma matriz unidimensional aninhada dentro de outra matriz dimensional . Assim, você ainda obtém os benefícios de velocidade do suporte a idiomas intermediários integrado. Talvez surpreendentemente, a matriz bidimensional é a mais lenta, e isso ocorre porque uma matriz bidimensional cedeu. Net é apenas uma classe sem qualquer suporte especial de tempo de execução. Aqui, deixa-me mostrar-te as minhas compilações. Frio. Se eu definir um ponto de interrupção, execute novamente o programa no switch para desmontagem views ons. Procure a linha correta aqui. Aqui está a matriz unidimensional. Você pode ver a instrução de elementos conjuntos familiares que grava o valor no elemento Você é um . E aqui está o jittery JAG primeiro uma instrução de elementos baixos para cargas, a referência à matriz interna e, em seguida, uma instrução de elementos de conjunto para direitos um valor em que matriz interna. Mas agora olhe para a matriz bidimensional. É simplesmente uma chamada para um conjunto estático. Métodos fora do seu método A Class um chamar para cada acesso de elemento, além de qualquer implementação está dentro desse método conjunto, isso é muito mais códigos para executá-los. As matrizes unidimensionais e irregulares. Agora você acabou de ver que uma matriz unidimensional é mais rápida do que uma matriz bidimensional, então podemos acelerar uma matriz bidimensional usando uma técnica chamada achatamento de matriz. Dê uma olhada neste três por três inteiros já desligados. Isso é um total de nove elementos que eu posso colocar para fora assim. Eu tenho codificado por cores cada linha de elementos. Agora eu também posso empacotar todos esses dados em uma matriz unidimensional como esta eu posso acessar e elementos dada a linha e coluna como esta. Então vamos tentar em código. Eu tenho um programa aqui que configura um 1000 por 1000 inteiro array e, em seguida, perder através de todos os elementos acessando-os um por um. Aqui está o mesmo casaco, mas com uma matriz unidimensional com um milhão de elementos. Eu ainda tenho os dois loops aninhados para acessar Todos subiu em todas as colunas, mas agora eu usei a fórmula de tradução para nivelar a linha e a coluna em um índice unidimensional . Que casacos você acha que é mais rápido? É difícil dizer. Na verdade, sabemos que a matriz unidimensional será mais rápida. Mas agora também temos as despesas gerais fora fazendo a multiplicação extra para traduzir a linha e a coluna em um índice achatado. A multiplicação extra irá compensar as despesas gerais da matriz bidimensional? Vamos descobrir. Estou executando o programa agora. Eu estava aqui, nós estamos. A matriz unidimensional ainda é a opção mais rápida, com 15 milissegundos em comparação com a matriz bidimensional. Com 22 milissegundos, a matriz achatada é 1,5 vezes mais rápida. Aqui está um gráfico com todos os resultados de desempenho. A matriz bidimensional é duas vezes mais lenta do que a matriz unidimensional. Mesmo no teste de achatamento, quando a matriz unidimensional tinha as despesas gerais extras fora de ter que fazer uma multiplicação, ainda é 1,5 vezes mais lento. Isso sugere que achatar matrizes bidimensionais é uma boa idéia. Talvez surpreendentemente, a matriz Jagged tem um bom desempenho, também. É apenas 1,3 vezes mais lento do que a matriz unidimensional em quando comparado com os resultados achatados, há quase igual a 15 milissegundos para o achatamento de uma matriz dimensional em 16 milissegundos. Para a matriz irregular, a diferença de desempenho é de apenas um milissegundos, que é de 6%. Então, como você deve usar uma corrida se você só tem uma dimensão fora do dia za, use uma dimensional apagada para o melhor desempenho. Se você tiver duas ou mais dimensões de dados, considere achatar a matriz. Se isso não for possível, considere usar uma matriz de jaqueta. Se não houver outra opção, use uma matriz multidimensional. 14. Dica 5: evitar a possibilidade de jogar exceções: nesta palestra. Quero dar uma olhada no uso responsável das exceções. Então, o que são exceções? A estrutura Net usa exceções para lidar com erros. Então é assim que funciona quando algo dá errado em um bloco de códigos que códigos podem lançar uma exceção. Usando as instruções throw os métodos aborta imediatamente na pilha é desenrolada, o que significa que a estrutura mantém executando instruções de retorno implícitas, pulando fora de chamadas de método aninhado em ir mais alto e mais alto até a pilha de chamadas até que atinge casaco com um bloco de captura tentativa. O quadro salta para o primeiro bloco catch que corresponde à exceção do trono e começa a executar o código Lá. Você pode capturar a exceção colocando um parâmetro na expressão catch, modo que com certeza soe como um monte de despesas gerais, não é? Quão grande você acha que a sobrecarga de desempenho será para lançar e capturar uma exceção? Bem, vamos descobrir. Eu escrevi um programa que repete uma operação muito simples neste caso, incrementando em inteiro um milhão de vezes. Aqui estão os primeiros métodos de medição que simplesmente incrementa e variável inteira que eu era. Aqui está o segundo método de medição, os mesmos códigos, mas depois de incrementar a variável o código lança na adesão operação inválida. Agora as declarações throw está dentro de um olhar try catch, isso não há necessidade para o framework dot net para começar a desenrolar a pilha em busca de um bloco try catch. Já estamos dentro de um livro de captura de tentativa, então a execução salta diretamente para a instrução catch, que neste caso, não faz absolutamente nada. Então, este código irá medir o executa despesas gerais fora de um único arremesso e captura. Não há sobrecarga adicional porque a pilha precisa ser desenrolada. Também não há despesas gerais porque fora códigos de manipulação de exceção porque, neste caso, o bloco catch é anti pronto. Aqui vamos nós. Estou a gerir o programa. Você esperava que as despesas gerais fora lançando uma exceção é incremento maciço de uma variável inteira um 1.000.000 vezes leva apenas seis milissegundos, mas lançar na exceção dois milhões de vezes leva um adicional 6,9 segundos, não meramente segundos segundos. O código, com manipulação de exceção, é um impressionante 1150 vezes mais lento. Isso leva ao principal tirar esta seção. Nunca use exceções em códigos de missão crítica. Justo o suficiente. Vou simplesmente evitar usar as declarações de lançamento no meu casaco, e estamos todos prontos, certo? Bem, não. Eu também preciso ter certeza de que as bibliotecas em classes base que eu uso também não lancem exceções. A realidade é que muitos métodos de classe usaram exceções para sinalizar uma condição não crítica para o chamador. Se eu quiser evitar essas exceções, não posso chamar esses métodos e diretamente. Mas eu vou ter que verificar meu incluir mais cuidadosamente para erros. Vejamos o caso de uso simples. Vou executar o programa que converte strings para introduzir. Dê uma olhada neste código. Tenho uma mensagem aqui chamada Preparar Lista. Ele usa um construtor de cordas para montar um número de cinco dígitos usando um gerador de números aleatórios. Quando a string está pronta, ela é armazenada nesta lista genérica. O código é repetido um milhão de vezes, então eventualmente eu vou ter um 1.000.000 de strings para analisar. Os métodos de medição estão aqui em baixo. O 1º 1 percorre todas as 1.000.000 strings em usos. O método de parte inteira para converter o fluxo toe um número. Agora, se algo der errado durante a análise, os métodos de partes lançará uma exceção anterior. Eu pego a exceção aqui em simplesmente suprimir o erro. Os segundos medem, os métodos fazem o mesmo. Percorra todas as cordas do Parsons, um por um. Mas agora eu uso a tentativa. Métodos de análise em vez do pároco não é tentar. As partes tentarão analisar o fluxo em simplesmente não fazem nada. Quando a análise falhar, ele nunca lançará uma exceção, então eu não preciso tentar pegar livro. Aqui. Você pode ver a diferença nas falas do pecado. Try Pars retorna valor booleano indicando se o pároco foi bem-sucedido, ele também tem um parâmetro de saídas para armazenar o resultado. Compare isso com os métodos de análise, que simplesmente retorna os resultados de passagem diretamente e não tem nenhum parâmetro ou argumentos para indicar se a análise foi bem-sucedida ou não. Certo, volta aos métodos da lista de preparação. Dê uma olhada na costa. Vês alguma coisa estranha no final do meu conjunto de personagens? Eu tenho uma letra extra X. O gerador de números aleatórios aqui gera um número entre zero e 10 então 10% de desconto no tempo que ele irá adicionar a letra X ao fluxo em vez de um dígito válido. Se eu colocar um ponto de interrupção um pouco além do método, chamar executar o programa em, em seguida, inspecionar o conteúdo fora da lista genérica. Em seguida, você vê que alguns números são inválidos porque eles contêm em X em vez de um dígito. Os métodos de análise e teste partes falharão nesses números apenas nas partes. Métodos lançará uma exceção quando isso acontecer. Assim, com base no que já sabemos sobre exceções em que elas são realmente lentas, esperamos que os métodos de análise de tentativa executem muito melhor do que o método parts. Quão grande você acha que a diferença será? Vamos descobrir. Eu vou executar o programa, uma medida a diferença no desempenho. Aqui vamos nós. E aqui estão os resultados. O Tri Parse Methods leva 618 milissegundos para Roma. Os métodos de partes leva 3727 milissegundos, então a análise é seis vezes mais lenta do que try pars devido ao tratamento de exceção extra. Então, vimos que as conversões de tempo de dados também podem ser problemáticas. Casaco crítico de missão. Devido ao fato de que muitos métodos de conversão lançam exceções quando os dados de entrada são inválidos, inválidos,existem outras situações comuns em que exceções são lançadas? Sim, isso é que eu vou te mostrar mais uma. Então aqui está outro programa, e neste programa eu estou fazendo o oposto. Fora do maior programa. Estou convertendo inteiros de volta duas cordas. Tenho uma mensagem de lista preparada aqui. Guardas. Agora preenche um individualista genérico com um milhão de inteiros aleatórios. Andi Down aqui é os primeiros métodos de medição, que loops sobre cada número inteiro na lista, e para cada número, ele usa uma tabela de pesquisa para converter esse número em uma string. Se você olhar para cima aqui, você verá que eu tenho pré definido no inter seu dicionário string para agir como para procurar tabela, indexando a tabela de pesquisa com um inteiro. Eu posso encontrar rapidamente a string que corresponde a essa entrevista. Então, de volta aos códigos de medição. Eu uso a tabela de pesquisa para encontrar a string e armazená-lo na variável s Se algo der errado, esta instrução catch vai pegar a exceção em suprimir a seta. Agora vamos dar uma olhada nos métodos de segunda medição. Faz exatamente a mesma coisa, mas tem um cheque extra aqui. Eu testo se o inteiro está realmente no dicionário antes de usá-lo para procurar o fluxo correspondente. Isso, exceto a verificação, evita uma exceção. E então eu não preciso de uma tentativa. Bloco de dinheiro. Certo, volta à mensagem de preparação da lista. Você vê o validador de entrada? É muito simples. Eu tenho 10 entradas no dicionário para os dígitos de 0 a 9, mas o gerador de números aleatórios gera números entre zero em 10. Então, em 10% de desconto nos casos, eu vou ter o número 10 na lista na tabela de pesquisa não tem uma entrada para esse número. Se eu colocar o ponto de ruptura logo além do médico, executar o programa e depois inspecionar o conteúdo da lista genérica, então você pode ver que ocasionalmente eu tenho um 10 na lista. O dicionário procurar, falhará para este número e somente a medida. Um método irá lançar uma exceção quando isso acontece. Então, com base no que sabemos sobre exceções na sobrecarga de desempenho fora das exceções, esperamos que a medida A seja mais lenta do que a Medida B. Então vamos verificar se vou executar o programa e medir a diferença de desempenho . Aqui vamos nós e aqui estão os resultados medir a leva 1180 milissegundos para executar a medida ser leva apenas 167 milissegundos, modo que a medida A é sete vezes mais lenta do que a medida ser devido à manipulação de exceção extra . Aqui estão todos os resultados de medição Combina em um único ref aqui estão partes e tente pars com o método de peças levando 3727 milissegundos em testes pars que precisam apenas 618 milissegundos porque não precisa lançar uma exceção se os dados de importação são inválido, esta é uma queda no tempo de execução de mais de 80%. Em seguida, o teste de segundos com as pesquisas de dicionário realizando uma olhada em manipular exceções leva 1180 milissegundos. Mas se verificarmos a chave primeiro e depois executarmos a pesquisa, evitaremos exceções todos juntos. Na corrida, o tempo cai para apenas 167 milissegundos novamente, uma queda no tempo de execução fora de mais de 80%. Então, quais são as melhores práticas para usar exceções? Bem, se você usar exceções em um ciclo crítico de missão, então só as usou para indicar uma condição fatal que requer que você fique totalmente a bordo do loop . Não use exceções para condições não fatais que você manipula simplesmente passando para a próxima iteração de loop. Não coloque. Tente capturar blocos em código profundamente aninhado ou em funções AP I de baixo nível, porque eles vão tornar seus casacos mais lentos. Coloque o tratamento de exceção o mais próximo possível do programa principal. Nunca use uma captura genérica. Instruções de exceção, que armazena em cache todas as exceções porque também detectará condições não fatais em. Não será imediatamente óbvio por que seu código é lento Se você estiver escrevendo um P. Eu não uso exceções para condições de limite não críticas, como converter dados inválidos ou falhar em uma operação de pesquisa. Considere o teste padrão de análise para isso com um valor de retorno booleano indicando sucesso e um parâmetro de saída para retornar dados. O que quer que faça, nunca use exceções para controlar o fluxo do seu programa. 15. Dica #6: use em vez de foreach: esta palestra vai ser tudo sobre otimização de loops. Um conselho comum que você costuma ouvir sobre acelerando loops é que você deve usar quatro declarações em vez de para cada um olhar sobre a coleção. Mas isso é verdade sobre, E se sim, quão grande é a diferença de desempenho entre os dois? Vamos começar olhando para a mecânica fora do Z 4 e para cada declaração, vou começar com quatro. Ao usar um loop for para acessar elementos em uma coleção, nós geralmente começa com um loop que conta de zero para o número off elementos na coleção Minds um. Em seguida, usamos no índice de expressão para acessar cada elemento. Por sua vez, um quatro Luke só é possível se você tiver acesso direto a quaisquer elementos da coleção, geralmente através dos colchetes. Na meditação extra em C afiado, a matriz lista lista lista genérica em classes de matriz. Todos oferecem suporte à indexação. Outra maneira de acessar elementos em uma coleção é usando em numerador nos bastidores. O para cada instrução começa criando um novo objeto numerador em outras navalhas tem apenas três membros. Um campo atual que contém o valor do elemento de coleta atual uma mensagem mover próxima para mover para o próximo elemento. Andi redefinir métodos para saltar de volta para o início da coleção. Portanto, cada instrução configura um loop que continua chamando mover próximo até chegar ao fim da coleção. Durante cada iteração de loop, o campo atual contém o valor dos elementos atuais. Então, qual declaração é mais rápida? É difícil dizer. Na verdade, os quatro estadistas exigem um índice, então no custo fora, ser capaz de acessar diretamente qualquer elemento pode ser alto. Mas, por outro lado, Ford cada precisa configurar em objeto arejador inem em, em seguida, chamar a próxima mensagem mover em cada loop, -lo oração. Então tudo depende qual é mais rápido, o indexador ou a movimentação. Próximos métodos Aqui estão os prós e contras de cada método. A declaração quatro é potencialmente a mais rápida por causa de sua simplicidade, mas requer uma coleção com um indexador. Em coleções não sei antecipadamente, em que ordem os elementos serão acessados através do indexador, então todos os valores terão que ser carregados na memória primeiro, pois cada um é mais complexo porque usa uma enorme cratera nos bastidores, e requer uma chamada para o movimento próximo métodos para avançar para o próximo elemento. Mas a vantagem em operação é que ele funciona em qualquer coleção. Outra vantagem é que o valor atual é calculado sob demanda, modo que toda a coleção não precisa estar na memória para ser inaugurada. Conselhos na Internet cerca de quatro e para cada um é misturado, alguns dizem sempre usado para. Mas outros dizem que as melhorias de desempenho não valem a pena. Então deixe-me mostrar-lhe um programa que mede o desempenho de ambas as declarações para uma variedade fora de coleções. Eu escrevi o programa que configura três coleções de 10 milhões de inteiros uma lista de array , uma entrevista genérica lançada em uma matriz inteira regular. As listas são inicializadas neste método. Aqui. Preparar listas. A mensagem gera 10 milhões de números aleatórios entre zero e 255 em anúncios. toam todas as três listas, em seguida, vêm para os métodos de medição. Aqui em baixo, há seis deles. Vou começar com a 1ª medida. Um é solto através de todos os 10 milhões de elementos fora da lista de matriz com um simples loop for e usa o indexador embutido fora da classe iranianos para acessar os elementos. Agora, a próxima mensagem medida A também percorre todos os elementos fora da lista de matriz. Mas agora, usando um para cada instrução em vez das quatro instruções, então nos bastidores para cada um criará em objetos numeradores que passam sequencialmente através de cada elemento na coleção. Os próximos dois métodos são a medida Seja um tias Medida B dois. Eles fazem exatamente a mesma coisa, mas com o genérico enterrado sua lista em vez da lista de array. E finalmente, eu tenho os métodos Medida C um e Medida C dois. Novamente, eles olharam através de todos os 10 milhões de inteiros, mas agora eles usam o array inteiro. Então você vê todas as três classes de coleção suporta ambos indexação em admiração, que será mais rápido. Vamos descobrir que estou executando o programa agora, então vamos esperar pelos resultados. E aqui estão os resultados. Usando quatro matriz caído leva 98º milissegundos usando para cada Alan Array leva centenas e três milissegundos, uma pequena diferença. Mas usando quatro e para cada um na lista genética, leva respectivamente 300 oitos em 501 milissegundos, e obtemos a maior diferença de desempenho ao usar quatro e para cada um em uma lista de arrays , respectivamente. 302 em 872 milissegundos. Vês isso para um aumento? A diferença é muito pequena na otimização A para cada dois ou quatro provavelmente não vale a pena o esforço. Mas para a lista genérica e a lista de array, a diferença é bastante grande. Por que é isso? Vamos dar uma olhada no frio intermediário. Vou começar com a Medida C um método que usa um loop simples for para acessar cada elemento em uma matriz inteira. Então aqui está o loop for. Estas três instruções de linguagem intermediária definir a variável em 20 Então saltamos para a localização um F em para comparar a variável I com o valor de 10 milhões. Se eu tiver menos de 10 milhões, continuaremos aqui em Executar o corpo do loop. Você pode ver a instrução de elemento de carga familiar aqui para acessar em elementos de matriz. Finalmente, os códigos continuam aqui, onde não somos um para a variável por cheque novamente. Se for menos de 10 milhões agora, vamos olhar para os mesmos casacos no método Medida C dois. É quase o mesmo desligamento na variável de índice para zero, saltando para o local 24 comparando o índice com o comprimento fora da matriz. Andi continua se for menos. Mas olhe para este pouco antes de executar o novo corpo. Temos quatro instruções. Local de carregamento para o local de carregamento. Três elementos de carga no local da loja. Um. Estas quatro instruções. Recuperar os elementos da matriz atual e armazená-lo no local um, que corresponde à variável I. Assim, a diferença entre um quatro e A para cada instrução para matrizes regulares é apenas estes quatro linguagem intermediária instruções sobre isso é por isso que a diferença de desempenho medidas entre os dois é tão pequena. O compilador realmente não cria em objetos numeradores usando correntes e mover em seguida. Em vez disso, ele emite um aspecto de quatro ligeiramente modificado para implementar a enumeração. Agora vamos olhar para as listas genéticas. Na medida. Seja um na Medida B dois métodos. A medida, seja um método, é muito simples. Primeiro, temos o familiar conjunto de instruções para o loop quatro aqui. Em seguida, ao acessar os elementos da lista, os códigos usam uma chamada virtual para um indexador. Chamadas recebem item. O valor de retorno é um inteiro que fica armazenado no local para. Mas a Medida B 2 é muito diferente. O para cada instrução começa chamando os métodos do numerador get in nos objetos da lista . A lista no numerador é na verdade uma estrutura e não uma classe em. Ele fica armazenado no local para os códigos, em seguida, chama. Mova o próximo no operador em e continua se o resultado for verdadeiro. Em seguida, ele obtém o valor dos elementos atuais chamando a propriedade, obter correntes e armazena os resultados no local um. Em seguida, o corpo do loop é executado, e então nós estamos de volta em movimento em seguida. Este casaco é muito mais complexo do que simplesmente acessar elementos da lista através do índice. Melhorado, ele corre mais devagar. Agora vamos olhar para a lista de matriz em métodos. Meça uma medida de £1 A. Para não há nada de especial na Medida A um, um loop regular for em uma chamada virtual para o indexador de item gets. Mas olha para esta parte aqui. A injeção de item get retorna e objetos, não um inteiro. Então, antes de podermos armazenar os resultados, ele tem que ser descaixa com esta instrução de descaixa aqui, e nós já aprendemos que o boxe e o desboxe introduzem um desempenho significativo na sua costa. Agora aqui é medido. A para este casaco parece muito semelhante ao culto lista genética na Medida B dois. Mas olhe para as diferenças aqui primeiro. A lista de matriz no numerador é uma classe em não strapped, que significa que o cool para obter atual é uma chamada virtual em vez de uma chamada regular. Isso pode parecer um detalhe trivial, mas a instrução colvert é, defato, fato, mais lenta do que a instrução de chamada. Mas em segundo lugar, obter retornos atuais com objetos e não um inteiro. Portanto, o resultado precisa ser descaixa novamente antes que ele possa ser armazenado, que acontece aqui com a instrução Unbox. Então aqui está uma importância. Não tire nenhum genérico. Os operadores sempre retornam o valor atual como um objeto. Então, se você usá-los toe em operar tipos de sobrevalor, eles vão descaixa em segundo plano sempre usado enumeradores genéricos, se possível. Aqui estão todos os resultados de medição combinados em um gráfico usando £4 para cada em uma matriz leva, respectivamente, 98 sob centenas e três milissegundos usando quatro e para cada uma em uma lista genética leva respectivamente 308 e 501 milissegundos em usar quatro e para cada uma em uma lista de matriz leva, respectivamente, 302 em 872 milissegundos. Vimos que há muito pouca diferença entre um quatro abaixo para cada quando em operar uma matriz regular. A razão para isso é que o compilador otimiza matriz e admiração emitindo um loop quatro ligeiramente modificado. Em vez de passar pelo aborrecimento fora, criando no operador objetos com listas genéricas, vemos uma diferença clara entre quatro. E para cada um, o numerador é instrut, que é um tipo de valor, e assim ele pode armazenar tipos de valor como inteiros de forma eficiente e ainda em admiração, ainda é 1,6 vezes mais lento do que usar um loop for. Então aqui faz sentido perfeito toe otimizar um para cada loop, reescrevendo seu em um loop for . E finalmente, ao olhar para uma lista de raios, vimos que há muito fora de desboxe acontecendo nos bastidores. O indexador retorna. Um objeto que precisa ser convertido em um inteiro no operador in é uma classe e não destruir em. Ele retorna o valor atual como um objeto, que novamente precisa ser convertido para um inteiro em operação em uma lista de matriz tem uma enorme penalidade . É dois pontos oito vezes mais lento do que usar um loop for. Então é assim que você escolhe entre quatro e para cada um. Se você estiver usando um array, não se preocupe em reescrever um para cada um em um quatro. A diferença de desempenho simplesmente não vale a pena. Ao usar uma lista genérica, usar um quatro instruções é 1.6 vezes mais rápido do que usar um para cada instrução, então reescrever para cada um em um loop for definitivamente vale a pena. E para listas de arrays, as melhorias são ainda maiores. Uma declaração de quatro em uma lista de arrays é 2,8 vezes mais rápido. Mas se você pensar sobre todo o boxe e unboxing acontecendo em segundo plano, você pode realmente querer considerar reescrever a lista de arrays para usar uma lista genérica em vez disso. E, finalmente, se você usar para cada um e você está em operar uma coleção fora tempos de valor, sempre certifique-se de que você está usando o Anoma Reiter genérico, que está no E uma interface de chá de ervas nominal em não o não genérico em numerador, que está na interface E inumerável em. A razão para isso é que o operador não genérico em irá sempre retornar o valor atual como um objeto, modo que seu revestimento compilado irá conter lotes fora da caixa e instruções de descaixa e usando um genérico em memórias que irá evitar isso. 16. Como funciona o coletor de lixo: esta palestra é por pedidos fora chupar. Engraçado quem me pediu para investigar a coleta de lixo? Obrigado pelo pedido, idiotas. Espero que gostem desta palestra, e também espero pronunciar o seu nome corretamente. Se alguém mais tiver algum pedido especial sobre o assunto que você gostaria que eu abordasse, envie-me uma mensagem e eu vou colocá-lo no meu roteiro ou lições futuras. Se você se lembrar na segunda palestra deste curso, aquela sobre memória de heap na seção de fundamentos, vimos o que acontece quando variáveis de tipo de referência saem do escopo. As variáveis que existem na pilha são destruídas nos objetos correspondentes no heap. Os objetos referenciados de R D continuam a existir e não são destruídos imediatamente. Mencionei brevemente que um processo separado chamado coletor de lixo periodicamente limpa esses objetos. Então, nesta palestra, vamos dar uma olhada mais de perto no coletor de lixo. O que é o coletor de lixo e como ele funciona? Vamos começar com um programa muito simples. Este programa tem apenas uma mensagem com uma matriz de objetos de variável local. A matriz é inicializada com cinco objetos nesses cinco objetos também residem no heap adjacente à matriz propriamente dita. Agora, para tornar as coisas mais interessantes, vamos remover os elementos de matriz dois e três sentados. Há elementos de matriz para conhecer os objetos correspondentes. Número dois e três ainda existem no calor, mas agora eles são de referência. Não há referências a esses objetos de qualquer lugar do casaco. O que acontece quando o coletor de lixo chutar a porta? Rede. Coletor de lixo é um coletor de marca e varredura, que sai. Há dois estágios distintos fora do lixo coletando um estágio de marca em um estágio de varredura durante o estágio de marca. O coletor de lixo marca todos os objetos de vida no assim neste exemplo, que seria a matriz em si e os objetos 01 em quatro. Os objetos dois e três, são ignorados porque não há referências a esses objetos de qualquer lugar no casaco. Em seguida, vem a fase de varredura todos os objetos que não têm sido marcados na fase anterior R D referência em, e nesta fase eles são de alocados a partir da pilha. Assim, neste exemplo, os objetos dois e três não foram marcados, e assim eles são os alocados em. Isso deixa um tipo de buraco na pilha. O coletor de lixo da rede do cão executa um passo adicional após a varredura, que é chamado de compactos. No estágio compacto, todos os orifícios na pilha são removidos, portanto, neste exemplo, o objeto quatro é movido para cima para preencher o buraco Esse objeto para deixar para trás a marca e varrer coletor de lixo é muito bom em localizar. Cada negócio referenciado objeto no heap em removê-lo. Mas também tem uma grande desvantagem. É muito lento. Durante o estágio de marca, o coletor de lixo deve inspecionar cada objeto na memória para determinar se ele é vida ou o referenciado. Se houver muitos milhares de objetos no heap, seu programa realmente congelará por um tempo como o coletor de lixo está inspecionando cada objeto. O processo também é muito ineficiente porque objetos vivos longos na pilha são verificados e verificados novamente, suportando cada ciclo porque eles poderiam ser d referenciados a qualquer momento. Assim, objetos vivos muito longos podem ser verificados centenas de vezes se ainda estiver vivo. A solução para esse problema é chamada de coletor de lixo geracional, o coletor de lixo de redes de ponto é geracional, e tem três gerações, que você pode visualizar como três pilhas separadas. Todas as novas alocações vão para a primeira pilha geracional chamada Geração Zero. Então, se revisitarmos o programa de teste com a matriz de cinco elementos com elementos dois e três, resolver não. Em seguida, os layouts de memória seriam assim. Tudo é o mesmo que antes, mas agora todos os objetos estão residindo na geração zero gerações. Um vem para o nosso anti. O primeiro ciclo de coleta faz uma marca e varredura em todos os objetos que sobreviveram ao movimento de varredura para a Geração um. Então, após um ciclo, o layout de memória se parece com esta matriz Z. Os objetos 01 e quatro da Andi sobreviveram à varredura e estão agora na primeira geração. Agora imagine que o programa continua neste momento. Ele coloca um novo objeto cinco em elementos de matriz para todas as novas alocações. Entre em uma geração zero para que os layouts de memória fiquem assim. Como você vê, esta é uma situação interessante. A matriz recita na geração um, mas seus elementos estão em gerações zero e um. Isto é perfeitamente válido. Agora. O coletor de lixo chuta novamente para um segundo ciclo. Todos os objetos de geração um se movem para geração para no novo objeto na geração zero se move para geração um se o programa continua e coloca um novo objeto seis em um raro elementos três, ele iria novamente para geração zero. Nós agora temos uma matriz em geração para referir-se a objetos na geração 01 como a novamente prova da violência. Então você pode estar se perguntando agora por que tudo isso está acontecendo? Por que essas três gerações? Bem, a resposta é muito simples. Gerações ajudam a limitar o número de objetos na geração zero. Cada ciclo de coleta limpa completamente a geração zero de todos os objetos. Assim, no próximo ciclo, o coletor de lixo só tem que inspecionar novos objetos que foram criados após o último ciclo. Claro, o problema não está desaparecendo. O coletor de lixo simplesmente moveu os objetos para outro lugar. Mas aqui estão as principais gerações que são coletadas com pouca frequência. O coletor de lixo assume que qualquer coisa que chegue à geração deve ser um objeto de vida longa que não precisa ser verificado com muita frequência, então isso resolve dois problemas. Primeiro, ele reduz o número de objetos na geração zero, então o coletor de lixo tem menos trabalho para fazer segundo, objetos de vida longa que sobrevivem em geração para eu não sou verificado com muita frequência, o que é exatamente o que queremos. O coletor de lixo geracional é um lindo algoritmo de alto desempenho, mas tem uma desvantagem importante. É ineficiente como o processamento de objetos grandes e longos. Considere um grande e longo deixando objetos será alocado na geração zero. Ele sobrevive ao primeiro ciclo, e o calor é compactado, que potencialmente move o objeto na memória. Então ele se move para a primeira geração. Ele é compactado em movimentos para geração para todos. Ao todo, estes são dois compactação e dois movimentos, Assim, um total off quatro operações de cópia de memória para um único objeto antes que ele chega em geração para no coletor de lixo ignora por um tempo. Se o objeto for muito grande, essas quatro operações de cópia por objetos podem introduzir custos indiretos de desempenho significativos. Portanto, a solução para este problema é ter dois heaps separados, um para pequenos objetos em um. Para objetos grandes, o design se parece com isso. Redes interiores que são muito quadris. O objeto pequeno. Ele, que trabalha com as três gerações que discutimos antes, e a grande ponta de cultura, a coisa especial sobre a grande pilha de objetos é que ele não usa gerações. Na verdade, ele tem apenas uma única geração, que é sincronizada com geração para fora do pequeno objetivo. Então, quando a coleta de lixo foram processos geração para fora do pequeno de sua equipe, ele também corre através de todo o grande objetivo. Outro fato interessante sobre o heap de objeto grande é que ele não compacta o durante o ciclo de varredura. É simplesmente funde blocos de memória livres juntos, mas não faz qualquer compactação para otimizar a quantidade total de espaço livre. Você pode estar se perguntando o que determina se um objeto é pequeno ou grande? Bem, o limite de tamanho é de 85 kilobytes. Qualquer objeto em 85 kilobytes para maior vai diretamente para o heap de objeto grande. Qualquer objeto menor do que esse limite vai para o heap de projeto pequeno. Ter esses dois montes separados resolve o problema de objetos grandes e longos. Eles não precisam mais ser copiados quatro vezes antes que eles acabem em geração para, mas em vez disso eles vão diretamente para o heap de objetos grande, que só é processado em geração para e nunca compactado. E aí está, o coletor de lixo de redes de cães é uma qualidade de lixo geracional ou que usa um ciclo compacto de varredura de marca . Ele tem pilhas separadas para objetos grandes e pequenos objetos. Se você pensar sobre isso, o coletor de lixo DOT Net faz algumas suposições muito específicas sobre objetos e vidas. Primeiro, ele assume que os objetos serão de elevador curto ou de longa duração. Todos os objetos de elevação curtos devem ser alocados, usados e descartados em um único ciclo de coleta. Qualquer objeto que deslize através das rachaduras, por assim dizer, é pego na primeira geração no próximo ciclo. Então, qualquer objeto que sobreviva a ciclos de coleta acaba em geração para e deve ser um objeto de vida longa. Além disso, qualquer objeto com mais de 85 kilobytes de tamanho é sempre considerado um objeto de vida longa. Ao olhar para a frequência de coleta fora das várias gerações, é claro que o coletor de lixo assume que a esmagadora maioria fora de objetos será de curta duração. Então eu posso resumir meu conselho de otimização de memória em uma única frase. Não vá contra essas suposições. Então, o que aprendemos? O coletor de lixo usa uma varredura de marca e ciclo compacto. O coletor de lixo tem dois montes separados para objetos grandes e pequenos. A pilha de objetos grande sobre o objetivo pequeno. O pequeno objeto que ele usa três gerações, todos os novos objetos são alocados na geração zero e progresso para geração para o objeto grande. Ele tem uma única geração que é processada em conjunto com geração para fora do pequeno objetivo. Além disso, o heap de objeto grande não compacta o heap. Para otimizar a memória livre, o coletor de lixo faz duas suposições importantes sobre tamanhos de objetos e tempos de vida. Um 90% de desconto em todos os objetos menores que 85 kilobytes devem ter vida curta para todos os objetos maiores do que 85 kilobytes devem ter vida longa. 17. Dica nº 7: otimize para coleta de lixo: Bem-vindo a partes para fora da série de palestras sobre coleta rápida de lixo. Nesta palestra, vamos olhar para vários otimização de desempenho é fazer o coletor de lixo executar o mais rápido possível. Mas primeiro, vamos recapitular o que aprendemos sobre o coletor de lixo. Na palestra anterior, o coletor de lixo de redes DOT usa uma varredura de marca no ciclo compacto para limpar objetos referenciados em D do calor. uso é separar montes para objetos grandes e pequenos. O grande ou píer neste pequeno objeto, os pequenos objetos que ele usa. Três gerações, todos os novos objetos são alocados na geração zero ons. progresso em direção à geração zero é coletado com muita frequência. Gerações de uma mão muito menos assim. Gerações ajudaram a limitar o número de objetos na geração zero. Cada ciclo de coleta limpa completamente a geração zero de todos os objetos no próximo ciclo , o coletor de lixo só precisa inspecionar novos objetos que foram criados após o último ciclo. A primeira otimização de desempenho baseada na memória que vamos analisar é simplesmente limitar o número de objetos que criamos na geração zero. Quanto menos objetos são criados, menos trabalho o coletor de lixo tem que fazer para limpar o heap. Existem duas estratégias que você pode seguir para limitar o número de objetos no quadril. A primeira é garantir que seus códigos não criem nenhum objeto redundante em qualquer lugar em segundo lugar para alocar, usar e descartar seus objetos o mais rápido possível para que eles já estejam alocados pelo coletor de lixo no próximo ciclo. Se você esperar muito tempo entre alocar, usando ao descartar seus objetos, você corre o risco de acabar em gerações uma ou duas. Então, para objetos de curta duração, você quer que seus casacos sejam o mais apertados possível. Vamos dar uma olhada em alguns exemplos. Aqui está um fragmento de código que loops 10.000 vezes em constrói uma string, usando um construtor de string com um frio para o método appends. Consegue ver o problema com este casaco? Dou-te 10 segundos para pensares. Aqui está a solução. O problema é que com a concatenação de string dentro da mensagem anexa, você vai se lembrar que strings são imutáveis, e assim a mensagem de duas strings na adição ambos cria objetos de string extra no heap para cada loop -lo oração o frio na parte inferior evita este problema por montagem, chamando upend duas vezes. A diferença é 40.000 objetos de cadeia de caracteres menos no heap. Isso é uma grande melhoria. Então aqui está outro exemplo. Vê se consegues perceber o problema. Dou-te 10 segundos outra vez. E aqui está a solução. Se você armazenar inteiros em uma lista de matriz, os inteiros são encaixotados no quadril. As listas genéricas evita esse problema usando na matriz inteira interna em vez de uma matriz de objeto, uma simples modificação para os códigos que resulta em 20.000 menos encaixotados em seus objetos no ok. Mais um exemplo. Um objeto pequeno e estático é inicializado, então muitos outros cultos são executados primeiro. Finalmente, o objeto realmente é usado. O que há de errado com esta foto? Dou-te 10 segundos, e aqui está a resposta. O objeto é pequeno, mas a diferença entre alocação e uso é muito grande, então há uma grande chance de que os objetos acabem na geração um ou dois antes de serem usados. Os códigos na parte inferior evita esse problema, meu primeiro, meu primeiro, tornando os objetos não estáticos lá, alocando é pouco antes de usar e finalmente definindo a referência do objeto para saber logo após o uso para sinalizar para o lixo coletor que foram feitos em que os objetos estão prontos para serem coletados. Se você não gosta de ter nenhuma atribuição em todos os seus códigos, você também pode envolver os códigos inferiores na mensagem, assim como o objeto de referência sair do escopo quando você sair dos métodos. É a minha solução preferida. A próxima otimização que você pode executar é ajustar a vida útil de seus objetos. O coletor de lixo assume que isso é quase tudo. Pequenos objetos serão de curta duração, e todos os objetos grandes serão elevador longo, então devemos evitar o oposto. Pequeno, longo elevador, todos tex ou grandes assuntos brevemente. É instrutivo visualizar essas combinações em um gráfico. Se eu plotar a vida útil do objeto horizontalmente no tamanho do objeto verticalmente, obtenho os gráficos a seguir. Os quadrantes inferior esquerdo e superior direito estão onde você quer estar. Essas combinações de objetos, tamanhos e tempos de vida combinam exatamente com as suposições do coletor de lixo. No canto superior esquerdo, no canto inferior direito? Quadrantes estão em desacordo com as suposições do coletor de lixo. Se o seu código contém muitos objetos de thes quadrantes, você está efetivamente trabalhando contra as suposições fora do coletor de lixo no desempenho fora de seu casaco provavelmente vai sofrer como resultado disso. Então, o que podemos fazer para entrar nos quadrantes corretos? Vamos começar com objetos. Tempo de vida para re fator grande, objetos de elevação curto que precisamos para aumentar o objeto. Vitalícia. Há uma estratégia muito simples para isso, que é chamado de pool de objetos. A idéia é que, em vez disso, muitas vezes descartar e objetos e alocar e novos objetos. Em vez disso, você reutilizar os objetos existentes. Como os mesmos objetos estão sendo usados repetidamente, ele efetivamente se torna um objetos de vida longa. Esta é uma estratégia muito popular para otimizar a pilha de objetos grandes. Então vamos olhar para um exemplo. Aqui está um fragmento de códigos que aloca uma grande lista de arrays, em seguida, usa-a duas vezes, descartando na realocação da lista entre usos. Como você melhoraria este casaco? Vou dar-lhe 10 segundos para pensar sobre isso, e aqui está a solução. Em vez disso, ao descartar ao realocar a lista, você limpá-la com uma chamada para a mensagem clara e, em seguida, reutilizar a lista para o segundo método, segundo método, chamar a nova camada. Os mesmos objetos de lista de matriz é usado uma e outra vez, sua vida é aumentada sobre os objetos efetivamente se torna longa vida. Essa alteração melhora o desempenho e reduz a chance de que o heap de objeto grande se torna fragmentado. Agora vamos olhar para o problema inverso. Temos um pequeno, objetos de elevação longo que devemos refratar ou em um objetos de vida curta. Como isso funcionaria? Aqui está um exemplo. Este casaco preenche uma lista de matriz com 10.000 objetos de par. Quilos cada cabelo contém dois dentro do seu. Então, o que há de errado com este código? Dou-te 10 segundos para pensares nisso. O problema é que a lista de matriz é um objeto grande, então ele vai para o objeto grande que ele é assume para ser longa vida. Mas a lista está cheia de pequenos objetos de par, 10.000 deles. Todos esses objetos vão até o heap de objeto pequeno em Geração zero, porque a lista de matriz está mantendo um dedo de referência cada Eisen. Todos esses pais nunca farão referência, e eles acabarão por se mudar para uma geração. Para a solução é usar outra estratégia popular refeitório em vez disso, tendo uma lista com dois inteiros em cada elemento de lista. Nós dividimos a lista de partes em dois inter separados gera. Como um inteiro é um tipo de valor, ele será armazenado com a matriz. Então agora só temos dois maiores aumentos no grande objeto que ele e absolutamente nada na geração zero. Problema resolvido. A terceira otimização que você pode executar é encontrar junho o tamanho de seus objetos. O coletor de lixo assume que quase todos os objetos lentos serão de curta duração. Todos os objetos grandes terão vida longa. Então, se tivermos os opostos em nossos códigos, objetos de elevação longa da escola ou grandes objetos curtos à esquerda, precisamos refratar o tamanho desses objetos para colocá-los de volta na carga correta. De acordo. Vamos começar com um grande objeto curto esquerdo para reduzir o tamanho desse objeto. Existem duas estratégias. Uma divisão do objeto de partes em subobjetos, cada um menor que 85 kilobytes ou dois reduziu o espaço de memória do objeto. Aqui está um exemplo fora da segunda estratégia. Um loop preenche o buffer com 32 mil, mas você pode ver o que há de errado com os códigos dele? Dou-te 10 segundos, e aqui está a resposta. O loop preenche o buffer com mordidas, mas o buffer é definido como uma matriz desligada de inteiros. O buffer contém 32.000 itens em Como um inteiro tem quatro bytes de tamanho, isso adiciona até 100 28 milhares de mordidas. Isso está acima do limite de objeto grande e, portanto, esse buffer vai diretamente até que o heap de objeto grande em é coletado em geração para a solução é re fator o buffer como ele morde buffer. Agora, as pegadas de memória fora do buffer são exatamente 32.000 bytes, que é menor do que os limites de objetos grandes. E assim ele recebe lojas sobre o pequeno objetivo na geração zero, assim como nós waas. Agora, vamos olhar para o problema inverso. Temos um pequeno, objeto de longa duração que devemos re fator em um grande, longo levanta objeto. Como isso funcionaria? A solução é, para qualquer e grande o espaço de memória fora do objeto ou para mesclar vários objetos juntos para criar um objetos maiores que podem ir para o grande objetivo. Então aqui está o exemplo final desta palestra, este casaco declara uma lista de matriz estática em. Então, em algum lugar no meio do caminho, o programa começa a usá-lo. O que há de errado com este código? Dou-te 10 segundos. Aqui está a resposta. É claro que o objeto se destina a ser um objeto de vida longa porque é declarado estático. Se também soubermos que a lista eventualmente conterá pelo menos 85 kilobytes de dias, hein? Então, é melhor inicializar a lista para esse tamanho. Isso garante que a lista vai diretamente na pilha de projeto grande porque se você não inicializar a lista, ele obtém a capacidade padrão, que fora do topo da minha cabeça é 16 kilobytes. Então, a lista vai para o heap de objeto pequeno na geração zero e, eventualmente, se move para geração para depois potencialmente, tendo passado quatro operações de cópia de memória inicializando a lista para o tamanho correto imediatamente, você evita a migração da geração zero para geração para totalmente. Pode parecer estranho que você pode otimizar o casaco fazendo objetos maiores, mas isso é exatamente o que estamos fazendo aqui. E às vezes funciona mesmo. Então, o que aprendemos para otimizar seu código de tal forma que o coletor de lixo seja executado mais rápido possível? Você precisa seguir essas estratégias primeiro, limitar o número de objetos que você cria segundos aloca usar em descarta objetos pequenos o mais rápido possível. Seward reutiliza todos os grandes projetos. Você quer trabalhar com o coletor de lixo e não contra ele. Assim, você deve garantir que todos os objetos em seus códigos sejam pequenos e de curta duração. Quatro grandes e longas vidas. Então, se acontecer de você ter objetos que são ou elevador exuberante e curto ou pequeno em elevador longo , você pode querer re fator-los para grandes assuntos brevemente. Você pode aumentar a guerra vitalícia, diminuir o tamanho fora do objeto e quatro pequenos objetos de elevação longa. Você pode diminuir o sinal de vida ou aumentar o tamanho. Todas essas mudanças irão beneficiar o desempenho do seu casaco. 18. Dica nº 8: use delegados rápidos: Nesta palestra, vou dar uma olhada mais de perto. Isso é delegados. Agora, se você não está familiarizado com o conceito de delegados, os delegados não é nada mais do que um tipo que envolve um mítico. Então, onde um tipo regular define que tipo de dados enfraquecem, armazenam, digamos, transmitem em suas datas, etc., um delegado define que tipo de método podemos chamar. Então, qual é o método para amadores sobre o que é o tipo de retorno a definir? Um delegado em C. Sharp, usei as palavras-chave dos delegados como esta. Este exemplo específico define uma chamada de método que espera enterrar seus parâmetros de entrada um parâmetro de saídas inseguras em um tipo de retorno vazio. Agora, esta declaração não define nenhuma variável ainda. Tudo o que temos neste momento é uma nova definição de tipo chamada adiciona Delegate, que descreve uma assinatura de métodos específicos para criar um novo delegados. Variável. Eu preciso fazer isso. Isso configura um novo tipo de variável desligada. São delegados. Então, como faço para assinar o valor para esta variável? Bem, primeiro, precisamos ter métodos em algum lugar em nosso código que correspondam exatamente à assinatura que configuramos anteriormente para inserir seus parâmetros de entrada. Um parâmetro de saída inteiro e um tipo de retorno void algo parecido com isso. Agora que eu tenho a implementação de métodos, eu posso atribuir a variável delicates como esta e então eu posso chamá-lo assim. Agora, o que você viu até agora é chamar um único elenco delegado. Este é um delegado que inicializei com uma única implementação de mensagem e quando invoco os delegados, ele chama os métodos únicos. Mas os delegados são muito mais poderosos do que isso. Eu também posso configurar um multi elenco delegados. Este é um delegados que pode chamar mais de um método de uma única vez. Digamos que eu tenho que adicionar métodos no meu casaco chamado É quente e acrescenta. Eu posso então montar os delegados assim. Observe o uso fora do mais é operador na terceira linha. Isso, como um método extra para um delegados existentes em efetivamente configura um multicast delegados. Qualquer delegado pode ser multi amaldiçoado simplesmente adicionando mais métodos a ele. Não há limites para o número off métodos que você pode pedir, mas por favor não vá ao mar novamente. Eu invoco os delicados como este neste vai chamar todos os métodos acrescenta em sequência na mesma ordem que eu adicione-los aos delicados. Então isso me fez pensar. Existe uma diferença de desempenho entre um elenco exclusivo e um multielenco delegados? Em? Qual seria a penalidade usando um delegado em vez de uma chamada de método regular? Vamos descobrir. Para testar o desempenho fora delegados eu escrevi o seguinte código. Começo aqui com os delegados familiares por adicionar dois números juntos. Aqui estão duas implementações fora das mãos métodos que são completamente idênticos e coincidem com a assinatura delegados. Então eu faço três testes no meu primeiro teste. Eu chamo os métodos de anúncios diretamente. Isso fornecerá uma linha de base. O segundo teste configura um elenco único. Delegados em chamadas duas vezes. Isso nos dirá o que a sobrecarga está desligada, usando os delegados em vez de chamar diretamente os métodos de anúncios. E, finalmente, eu enviei um multi personalizado delegados, atribuí-lo com dois métodos e, em seguida, invocar que delicado. Uma vez. Isso irá comparar o desempenho do multi elenco em delegados exclusivos do elenco, e aqui estão os resultados. Há uma sobrecarga de desempenho de 9% ao usar um delegados em vez de chamar o manipulador diretamente, isso não é muito ruim, mas usar um multicast delegados é mais do dobro de lento como chamar um exclusivo como delegados duas vezes. Whoa. A razão para isso é devido à forma como os delicados são implementados. Nos bastidores, um delegados é implementado pela classe de delegados multi elenco. Essa classe é otimizada para delegados de elenco uni. Ele usa uma mensagem em uma propriedade de destino para chamar diretamente um único método. Mas para delegados multi-cast, a classe usa uma lista de invocação em listas genéricas internas, mantém referências a cada método, e eles são chamados em seqüência as despesas gerais fora. Passar pela lista de invocações é o que causa essa grande diferença no desempenho. Por último, permitam-me que vos dê um conselho. Às vezes vejo frio assim. Esta linha de código define uma variável demagoga em, em seguida, inicializar. É com um métodos DouBie que não faz nada. A vantagem de pré-inicializar a variável delicada é que não temos mais que verificar se a variável é não. Então, em vez de fazer isso, agora podemos simplesmente fazer isso independentemente. Se os delegados tiverem sido inicializados, o código sempre funcionará. Os delegados vão chamar a mensagem fictícia em não fazer nada ou chamar o manequim bagunça na Duthie sinais mensagem em sequência. Mas, por favor, não faça isso. Você viu nas medições de desempenho que os delegados multicast são muito mais lentos do que exclusivos como delegados. Então, o simples ato de pré-inicializar a variável delegado realmente faz o casaco mais do que duas vezes um lento. Então aqui está o meu conselho sobre delegados rápidos em C afiado. A sobrecarga de desempenho fora usando os delegados é de apenas 9%. Na maioria dos casos, isso é perfeitamente aceitável, e por isso sinta-se livre para usar delegados em qualquer lugar em seu casaco onde é conveniente Breath se você deve ter o maior desempenho possível, em seguida, remover todos os delegados de seções frias de missão crítica e aproveite o desempenho extra de 9% de desconto. E você sempre deve evitar o uso de delegados multielenco em Mission Critical Coast porque eles são mais do que duas vezes mais lentos, tão únicos quanto os delegados. 19. Dica nº 9: crie uma fábrica de cursos rápida: Nesta palestra, vou ver se consigo acelerar um casaco muito comum. Construir a fábrica da classe. Agora você deve estar se perguntando, o que exatamente é uma fábrica de classe? Simplificando, uma fábrica de classe é uma classe especial que constrói outras classes sob demanda com base em algum tipo de dados de configuração externa. Muitas vezes, você vê fábricas de classe sendo usadas ao acessar bancos de dados em códigos. Nós simplesmente queremos acessar o banco de dados em. Nós realmente não nos importamos se os dados são armazenados em um banco de dados do servidor de sequelas da Microsoft no banco de dados Oracle ou no banco de dados Maya Scwill. Trata-se de um detalhe de implementação com o qual os códigos não devem ter de se preocupar. Então, em códigos, você pode ver algo assim. Neste exemplo, A fábrica de Conexão de Classe é uma fábrica de classe que sabe como criar um objeto de conexão de banco de dados para os dados de vendas. Portanto, se houver uma configuração em algum lugar em um arquivo de configuração que especifica que todos os dados de vendas são armazenados em um banco de dados de artigo, a fábrica de classe pode usar essas informações para retornar pontos de dados do sistema. Clientes Oracle ponto classe Oracle Connection. Você pode ver como fábricas de classe, simplesmente cinco o frio enormemente. Você não precisa mais se preocupar com onde determinados dados são armazenados. A classe que fabrica conhece retornará automaticamente os objetos de conexão corretos para cada solicitação. As fábricas de classe têm outra grande vantagem. Eles nos permitem mover dados em tempo de execução se, em um determinado ponto, os dados de vendas migram para um meu banco de dados SQL, tudo o que precisamos fazer é atualizar o arquivo de configuração na próxima solicitação de dados, a fábrica de classe ler os dados de configuração modificados e retornar A My SQL objetos de conexão Em vez disso Para construir uma fábrica de classe, você precisa concluir este fragmento fora da camada. Assim, as entradas é uma string que contém o tipo fora da classe a ser. Em vez disso, ela ated na saída desejada é uma instância real fora dessa classe agora, este é realmente um problema difícil de resolver. O mais próximo que conseguir é compilar. O tempo é algo assim. Isso funciona muito bem porque o compilador pode compilar qualquer cenário possível que possamos encontrar em tempo de execução. Mas a desvantagem é que temos que antecipar cada uso possível fora da fábrica da classe de antemão. Por exemplo, é completamente impossível mover os dados de vendas para um banco de dados SQL se não tivermos antecipado o uso fora do meu SQL com instruções de caso correspondentes antes de mãos dadas. Portanto, este casaco não é muito útil em uma fábrica de classe, mas tem o melhor desempenho possível. Adicionarei o código à minha medição mais tarde para servir como um valor de referência. Ok, então uma declaração switch não será suficiente. O que mais temos? Outra possibilidade é usar a reflexão. O sistema de reflexão adulto, espaço de nome tem uma classe muito agradável chamada ativador que podemos usar para construir qualquer tipo objetos que gostamos com base no nome de seu tipo. Então eu posso reescrever o método anterior para usar reflexão em vez disso, e ele será algo parecido com isso. Agora isso vai funcionar perfeitamente em todos os cenários, eu posso jogar em qualquer tipo de nome que eu gosto em. Se o tipo existir, a classe ativador construirá a instância de classe correspondente. É perfeito. No entanto, esse resfriado tem um grande problema. Reflexão é muito lento. Vamos ver o quão lento em um momento quando eu executar o benchmark, mas confie em mim quando eu digo que isso vai introduzir um enorme revés de desempenho em seu casaco isso nem sempre tem que ser um problema. Podemos supor que as conexões de banco de dados não serão criadas com tanta frequência. Além disso, abrir uma conexão de banco de dados é uma operação muito mais lenta do que construir a classe, então uma desaceleração na construção dificilmente será perceptível para o usuário final. Devido a isso, muitas vezes você vê a classe ativador aparecendo em C revestimento afiado em revestimento não crítico. Não há absolutamente nada de errado em usar um pouco de reflexão para atender às suas necessidades. Mas o que é sobre frio crítico, onde o desempenho superior é crucial? Felizmente, há outra solução. Eu vou mostrar a vocês um truque de sopro mental para construir qualquer tipo de classe em tempo de execução com um desempenho que é comparável às instruções switch. Confira isso. Primeiro, vamos voltar ao problema básico que precisamos para completar esse método. Este é um método genético que pode construir a qualquer momento, mas uma coleção de métodos específicos também seria bom. Então vamos dizer que eu preciso construir uma classe chamada minha classe tipo um. Então eu precisaria da seguinte mensagem em linguagem intermediária comum. O corpo da mensagem vai ficar assim. Então aqui está o convidado fora do truque. Eu vou criar dinamicamente um delegados em tempo de execução direita essas duas instruções de linguagem intermediária nele e, em seguida, chamar os delegados para criar a instância de classe. É assim que funciona. Minha fábrica de classe primeiro recebe a string de nome de tipo, que descreve o tipo fora da instância de classe a ser criada. A primeira coisa que a fábrica de classe faz é verificar em um dicionário se os delegados correspondentes já foram criados. Se assim for, a fábrica recupera os delegados do dicionário e chama-o diretamente para criar a classe. Instância ainda sabe que a fábrica cria um método dinâmico em escreve os novos objetos e retorna instruções nele. Em seguida, envolve os métodos em um delegado e armazena os delegados no dicionário no caso precisarmos dele novamente mais tarde. Finalmente, ele chama os delegados para criar a instância de classe. Criar um método dinâmico delicates é muito lento, ainda mais lento do que chamar a classe ativador. Mas aqui está a coisa. Uma vez que temos um delegado, podemos continuar chamando-o uma e outra vez para criar mais instâncias de classe. Andi chamar um delegado é muito, muito mais rápido do que usar a classe ativador, então só haverá um atraso ao criar os primeiros objetos de um determinado tipo. Qualquer carvão subsequente será super rápido. Ok, então vamos dar uma olhada no meu programa de benchmarking para esta palestra. Você pode ver nesta declaração constante aqui em cima que eu vou interessar, e ela comeu um milhão de objetos. A primeira mensagem mede um loop um milhão de vezes usa as instruções de switch estático para construir os objetos que você pode ver aqui. Que o meu programa cria instâncias de construtor de cordas não suporta mais nada. Assim, as instruções switch é um pouco tolo porque ele suporta apenas uma única classe em não pode ser modificado de qualquer forma após a copulação. Mas manteremos aqui para servir como um valor de referência. Isso significa o melhor desempenho possível ao criar um milhão de objetos. Em seguida, é Medida B, que usa a classe ativador para criar os construtores de string realmente simples e códigos limpos em. É realmente uma pena que não vai funcionar muito bem. Você vai ver em um momento como a reflexão realmente é lenta. Finalmente, aqui está Medida C. Ele usa uma fábrica de classe chamada recebe criador de classe. Esta fábrica de classe retorna um criador de classe, que você pode ver é realmente um delegado para um método sem quaisquer parâmetros não retorna um objeto. A próxima linha cria esses construtor de string simplesmente chamando os delegados. Então toda a magia acontece na fábrica da turma. A primeira coisa que a fábrica de classe faz é tentar recuperar os delegados do dicionário . Se o dicionário ainda não contém os delegados em que a fábrica se move, obtém as informações do construtor para o construtor de string. Em seguida, cria um método dinâmico. Dan's escreve o novo objeto em instruções de retorno no método. Finalmente, ele envolve os métodos em um delegados em lojas. Os delegados no dicionário finalmente aqui embaixo os principais métodos do programa chama todos os três métodos de medida e exibe o seu próprio tempo em milissegundos. Ok, vamos ver como essas três técnicas se medem. Vou executar o programa. Confira isso e aqui estão os resultados. A instrução switch foi executada em 131 milissegundos. A conversa de classe ativador 11,377 milissegundos sobre o método dinâmico falar 673 milissegundos. Aqui estão os resultados em um gráfico. Então a instrução switch construiu um milhão de construtores de cordas em 131 milissegundos. Isso corresponde a 7633 objetos criados milissegundos par, mas a classe ativador fez muito, muito pior. Foram precisos 11.377 milissegundos para construir os construtores de cordas, o que corresponde a apenas 87 objetos por milissegundos. A classe ativador é 86 vezes mais lenta do que as instruções switch. E finalmente, aqui estão os resultados da mensagem dinâmica. O delegado de métodos dinâmicos construiu um milhão de construtores de cordas em 673 milissegundos. Isso corresponde a 1485 objetos por milissegundos, portanto, os métodos dinâmicos são cinco vezes mais lentos do que as instruções switch. Mas é quase 17 vezes mais rápido que a classe ativadora. Certo, então o que aprendemos? Uma fábrica de classe é uma classe que constrói outras instâncias de classe sob demanda usando informações de configuração externa . Uma maneira comum de implementar fábricas de classe é com a classe ativador, mas a classe ativador é 86 vezes mais lenta do que a compilação estática. Casaco. Uma solução muito melhor é usar métodos dinâmicos, delega os métodos dinâmicos. Os delegados são apenas cinco vezes mais lentos do que os códigos de compilação estáticos em 17 vezes mais rápido do que a classe ativador. Em outras palavras, se você substituir a classe ativador por delegados de mensagens dinâmicas em sua fábrica de classe, você não vai acelerar seu casaco por um fator off 17. 20. Estão na pilha vale o problema?: Nas últimas palestras, analisamos as classes de coleção, incluindo a lista de array, a lista genérica em vários tipos de raise nativo, uma dimensional, bidimensional e matrizes irregulares. Há uma coisa que todas essas classes de coleção têm em comum. Todos vivem na pilha. Mas e se eu te dissesse que é realmente possível em C? Dedo afiado aloca na matriz de entrevistadores diretamente na pilha, tendo uma coleção off inteiros fora da pilha como suas vantagens. Se você olhar para o heap, o heap é gerenciado memória. Isso significa que o coletor de lixo ocasionalmente move blocos de memória em torno do dedo do pé otimizar o espaço livre. Mas esta pilha funciona de forma diferente. A memória na pilha é alocada quando seu código entra em um método como D alocado. Quando você retorna de um método alocado, memória nunca é movida. Isto tem uma vantagem importante. Se pudéssemos de alguma forma obter um ponteiro para empilhar memória, ele permanecerá válido até que a memória pilha é o alocado. Mas obter um ponteiro para aquecer a memória é muito mais difícil porque o coletor de lixo pode mover a memória em outro lugar a qualquer momento para obter um ponto ou dois memória de calor com tem que corrigir um bloco fora de memória no lugar no heap, dizendo ao coletor de lixo Você não pode tocar esse bloco fora da memória até que eu termine com isso tudo esse gerenciamento de memória cria uma sobrecarga de desempenho, então não é razoável esperar que o ponto de operações na pilha seja mais rápido do que o mesmo ponto de operações no Então vamos olhar para algum código novamente. Aqui está o programa que eu usei anteriormente para comparar o desempenho fora da lista de matriz a lista genérica na matriz nativa. Mas eu adicionei outro método de teste na medida inferior D. Vamos dar uma olhada. Começo declarando códigos inseguros. Bloco em C. Ponto afiado de operações só são possíveis dentro de blocos inseguros, portanto, este é um passo obrigatório. Então eu uso essas palavras-chave preso Um olhar para alocar na matriz fora 10.000 inteiros diretamente na pilha. O valor de retorno dessa operação está no ponteiro do entrevistador que aponta e diretamente na memória da pilha. Então dois aninhados soltos. O loop externo repete os experimentos 10.000 vezes. O olhar interno percorre todos os 10.000 elementos fora da matriz e ciência cada um deles. Você vê que mesmo que lista é um entrevistador, ponteiro, a sintaxe, acesso do dedo do pé e elementos é exatamente o mesmo como se lista seria uma matriz inteira. Os colchetes apagam. Intacs ainda funciona aqui. Aqui em cima estão os Métodos de Medida C. Esse método faz exatamente a mesma operação, mas ele usa uma matriz inteira regular no heap para que possamos comparar o desempenho. Vamos verificar os códigos. Aqui vamos nós. O inteiro regular de Ray leva 808 milissegundos para ser executado, mas a matriz Stack só precisa de 764 milissegundos. O array Stack é cinco pontos, 4% mais rápido. Vamos dar uma olhada nos bastidores e ver o que está acontecendo em linguagem intermediária. Vou mudar os ambientes para pontos de quebra do centro de modo de depuração, executar o código novamente e, em seguida, mudar para desmontagem vista para que possamos ver as instruções de linguagem intermediária em. E aqui estão os códigos para as atribuições regulares de matriz de inteiros. Você vê que este é o arranjo familiar de três instruções de localização de carga e um elemento de conjunto. A variável de lista é armazenada no local da variável. Um on é carregado na pilha de avaliação, em seguida, o I Valuable é carregado duas vezes, uma como o índice de matriz e uma vez como o valor a ser definido. Finalmente, a construção de elementos de conjunto armazena o valor no Índice Dado. Agora vamos olhar para a operação Poynter em códigos gerenciados. Se eu rolar, aqui estão as mesmas atribuições nos métodos medida D. Primeiro, o ponteiro da lista é carregado na pilha de avaliação, Em seguida, a variável I no número quatro são carregados, multiplicados juntos e adicionados ao valor da lista. Isso faz sentido se você considerar que um inteiro é quatro mordidas na memória, o casaco é simplesmente calcular o endereço de memória para escrever, que é lista mais por que multiplicado por quatro. A próxima instrução é carregar Localização três, que carrega o valor a ser escrito na pilha de avaliação. E finalmente, temos uma loja em instruções diretas sobre isso. Instrução grava o valor diretamente na memória. Então, talvez surpreendentemente, esses sete pontos ou instruções realmente são mais rápidos do que as quatro instruções de matriz na medida. Método C na diferença é porque o conjunto elementos direitos de instrução na memória gerenciada , enquanto o armazenamento em instrução direta grava diretamente em um endereço de memória fixa sem ter que tirar qualquer tipo de gerenciamento de memória em conta. Finalmente, deixe-me executar o programa novamente. Mas desta vez, com todas as medidas, métodos habilitam, Eu vou comparar a lista de matriz, a lista genética, a matriz de entrevistas nativas, mãos do inteiro ou um alocadores na pilha. Aqui vamos nós. E aqui estão os resultados. Deixe-me mostrar-lhe um gráfico dos resultados, que sejam fáceis de comparar. A lista de matrizes leva 9314 milissegundos para ser executada. A lista genérica é muito mais rápida. São 2704 milissegundos. O array inteiro é ainda mais rápido. São apenas 745 milissegundos. Mas essa matriz de pilha supera todos com um tempo de execução de apenas sete centenas e 13 milissegundos. A diferença de desempenho entre a matriz de inteiros nativos na matriz na pilha é 4%. Isso é muito menor do que a diferença entre, digamos, o array inteiro na lista genérica, que é 72%. Então, se você otimizar alguns códigos substituindo uma lista genética pela matriz nativa, que já lhe dá um aumento de desempenho de 70%, vale a pena ir ainda mais longe e transformar o array em uma matriz baseada em pilha apenas para o ganho extra de apenas 4 a 5%. A resposta provavelmente não é. A diferença de desempenho é tão pequena que pode ser facilmente um acaso de medição. Para testar esse cenário, fiz algumas modificações no meu programa. Vamos dar uma olhada. Eu simplesmente encontrar o programa para apenas olhar para a matriz regular na pilha neccesary cada métodos de medição espera parâmetro que indica o número fora dos elementos da matriz para alocar . A medição é repetida 5000 vezes em cada método retorna ao tempo total de execução em milissegundos. Aqui estão os principais métodos de programa, com o loop que repete o experimento para números de diferença fora dos elementos da matriz. Então, quais são os resultados? Eu realmente preparei os resultados com antecedência em colocá-los em um gráfico. Então aqui está o desempenho para ambos os tipos de corrida para os tamanhos de corrida de 0 a 49 elementos. Bastante inconclusivo, você diria? Mas talvez a diferença de desempenho se torne significante como tamanhos maiores de array. Então vamos testar isso também. Aqui estão os resultados novamente, mas agora, para uma corrida tamanhos entre um milhares em 100.000 elementos novamente inconclusivo. Não há um vencedor claro. Então, quando você deve usar Stack baseado na raça? A resposta é quase nunca, certamente não para otimização de desempenho, as melhorias são pequenas, apenas alguns por cento, e muitas vezes a matriz baseada em pilha é realmente mais lenta do que uma matriz baseada em calor regular. O Eso. A pilha é limitada em tamanho. As matrizes baseadas em pilha só podem armazenar até 10 megabytes de dados antes de ficar sem o Stacks Base. Para te dar uma ideia, tentei alocar 10 milhões de inteiros na pilha. Tenho uma exceção de estouro de pilha. Então você deve estar se perguntando, por que isso é empilhar uma fechadura? As palavras-chave estão disponíveis? Bem, a resposta é fornecer na interface para outras linguagens de programação que aloca arrays na pilha. Quando você está escrevendo casaco beira-mar que tem que interface com o Windows um P I ou bibliotecas escritas em C ou C mais, mais o estoque. Alex Keywords permite configurar um buffer de memória como um tipo de gateway entre esses idiomas na memória de gerenciamento dot net. Então, em conclusão, use apenas pilha um bloqueio para interface com revestimento não gerenciado em. Não o use para otimizar um array porque provavelmente não vale a pena. 21. Como posso usar ponteiros em C#?: nesta palestra, eu vou falar sobre dicas agora. Ponteiros não são usados muito, mas o C Sharp uma linguagem não suportá-los, mas por padrão ponto de operações são proibidos porque eles são potencialmente perigosos. Se você usar ponteiros em seu casaco, o tempo de execução do donut não pode mais garantir que a memória nunca será corrompida. E é por isso que eles são proibidos por padrão. No entanto, as operações pontuais ficam disponíveis. Se você entrar em suas propriedades de projeto em habilitar revestimento inseguro, então você pode usar as seguintes declarações e instruções em seu casaco. As palavras-chave não seguras habilitam o ponto de operações. Você pode usar as palavras-chave para marcar blocos fora do revestimento que usam ponteiros. Ou você pode colocar as palavras-chave em uma declaração de método para tornar todos os métodos inseguros. Quando você declara uma variável adicionando e Asterix no final do tipo, você declara um ponteiro fora desse determinado tempo. Então, neste caso, eu estou declarando um ponto de mordida ou as palavras-chave fixas, pinos e objetos na memória, dizendo ao coletor de lixo para não mover os objetos ao redor no calor até que terminemos com isso. Como o objeto é fixo, você pode usar o operador de um percentual para obter o endereço de memória fora dos objetos. Então, neste exemplo, eu estou corrigindo em objetos na variável Q no calor, e então eu obter seu endereço com o operador e percentual, e, em seguida, um sinal de que o endereço de memória para o ponteiro bys na variável p, o Asterix operador de referências nomeador, o que significa acessar os dados como esse local de memória. Então, neste exemplo, eu estou lendo uma única mordida no endereço atual fora do ponteiro. Você também pode tratar ponteiros como uma corrida. A operação do indexador lê dados em deslocamentos para o endereço do ponteiro de correntes. Neste exemplo, estou lendo uma única mordida no local da memória. Duas mordidas à frente do endereço atual fora do ponto. Ou você pode alterar o endereço de memória fora do ponteiro simplesmente adicionando ou subtraindo de seu. Neste exemplo, estou atualizando o endereço de memória atual do ponteiro avançando três mordidas à frente. Ponto de suporte foi adicionado ao tempo de execução dominante por três razões. Um dois auxiliares de interoperabilidade com código não gerenciado para dar suporte a um C ou C mais. Além disso, não é compilá-lo e três para torná-lo fácil ponto porta de algoritmos baseados para ver nítida. Então deve ser usado ponteiros em C Sharp ou apenas manter uma corrida. Bem, vamos descobrir. O exemplo clássico para usar ponteiros em C. Sharp é ao fazer manipulação de imagem. As imagens, que são carregadas na memória, são gerenciadas pelo sistema operacional. Os dados da imagem estão na memória não gerenciada como não na pilha de escuridão. Portanto, há apenas duas maneiras de interagir com os dados da imagem. Eles estão usando cidades de pixel de alto nível, definir métodos de pixel ou obtendo um ponteiro para a memória não gerenciada. Eu escrevi um pequeno programa que carrega imagem na memória e, em seguida, executa uma transformação em tons de cinza em sua Vamos dar uma olhada. Começarei com os principais métodos aqui em baixo. Minha imagem de teste é uma foto fora 19 anos 70 modelo Lane, afirmam Burke. Esta imagem é o padrão ouro para testar algoritmos de processamento de imagens. Eu amo a imagem na memória, em seguida, chamado a medida de uma bagunça é executar a conversão em tons de cinza usando o pixel get e definir métodos excell e, em seguida, escrever os resultados de volta para o disco. Então eu faço a coisa toda novamente, mas desta vez eu chamo a medida Ser Mensagem, que usa o ponto de operações para executar a conversão em tons de cinza para cima. Aqui estão os métodos de conversão. Medida A recebe a imagem como um parâmetro percorre cada pixel. Usando estes 24 perder contra a cor fora do pixel com um get pixel médico. A fórmula aqui executa a conversão para escala de cinza. Em seguida, crio um novo valor de cor, usando a variável de escala de cinza e modificando o pixel atual chamando Seth Pixel. Agora compare isso com a Medida B. Começo com uma chamada para bloquear bits que solicita acesso ao dater de imagem bruta. Então, neste bloco de carros inseguros, obtive um ponteiro de mordida para os dados da imagem chamando os dois métodos de ponteiro. A fórmula de conversão em tons de cinza é aqui notas que eu uso uma sala de índice para obter os valores vermelhos verde e azul no endereço do ponto atual e uma e duas mordidas à frente do endereço atual. Este próximo bloqueio de códigos grayscale valor em tons de cinza nos valores de pixel vermelho, verde e azul, escrevendo repetidamente para o ponto de endereço atual avançar o ponteiro por uma mordida. E finalmente desbloqueei os dados da imagem ao retornar o tempo de execução em milissegundos. Então, qual método vai ser mais rápido, você acha? Deixe-me executar o programa e descobrir agora. Como eu disse antes, eu estou executando um programa de captura de tela agora neste programa coloca pressão sobre a CPU na memória livre disponível. Sobre isso distorce os valores de desempenho. Então eu realmente preparei esta seção com antecedência e coloquei os valores de desempenho não distorcidos em um gráfico. Aqui estão os resultados que eu executar o teste três vezes como o tempo de execução para executar uma conversão em tons de cinza . Usando obter pixel e definir pixel é entre 146 ons 184 milissegundos. Mas ao usar ponteiros, o tempo de execução cai para apenas sete milissegundos. Para todos os três testes, o ponto de operação é um esmagador 96% mais rápido do que o método get pixel auscultador . Finalmente, deixe-me mostrar-lhe os resultados. Vou abrir um navegador de arquivos em selecionar a imagem original e a imagem se parece com isso. As saídas fora da medida A e Medida B estão aqui. Como você pode ver, é uma conversão perfeita em tons de cinza nas saídas desligadas. Ambos os métodos são idênticos. Então, analisamos a manipulação de imagem em C nítido e descobrimos que os ponteiros dão um enorme aumento de desempenho de 96%. É aceitável usar ponteiros? Nesse cenário, a resposta é sim. O principal take away para esta palestra é sempre usado. Ponteiros para manipulação de imagem em ponto net. Você realmente não tem escolha porque os dados da imagem estão na memória não gerenciada. Na alternativa obter pixel e definir pixel métodos são muito lentos. Posso generalizar este conselho nas seguintes diretrizes. Primeiro, se você estiver interagindo com dados na memória não gerenciada, tente usar métodos get e set de alto nível para ler e gravar os dados. Isso evitará ter que usar as palavras-chave não seguras em Marcar sua Assembléia como insegura. Se esta rota não é aceitável porque você precisa ler e escrever uma grande quantidade de dados sobre as cabeças acima fora do indivíduo get and set métodos torna-se muito grande. Zen. Obtenha um ponteiro para os dados em vez de modificar a memória diretamente para manipulação de imagem . Sempre use ponteiros porque os dias de imagem eu sempre serei muito grande para qualquer outro tipo de acesso de alto nível. É perfeita prática aceita para manipulação de imagem. Bibliotecas escritas em C afiado para serem inseguras 22. Dica #10: acelere o código com ponteiros: Na palestra anterior, analisamos o uso de ponteiros para manipulação de imagem em C afiado. Agora, manipulação de imagem se qualifica como um cenário em que estamos interagindo com código não gerenciado. O sistema operacional carrega memória de imagens em seu próprio heap não gerenciado. E a única maneira de obtermos os dados da imagem é solicitando um ponto de mordida para esse cenário específico. Claro, podemos usar dicas. Mas e outros casos de uso? É útil implementar estruturas de dados como listas vinculadas e árvores binárias com ponteiros ? Ou deveria ser apenas uma corrida para descobrir? Precisamos comparar com precisão as operações de desempenho fora do array e as operações de ponteiro para ver se há alguma diferença entre elas. Então vamos imaginar por um momento que todo o meu sistema operacional está realmente escrito em C afiado. Eu estou executando esses códigos em um macro pro, e eu vou fingir que o sistema operacional sempre X é realmente escrito em C afiado . Então, quando eu carrega na imagem na memória, os dados da imagem são imediatamente disponíveis como um gerenciado por tary. Então eu escrevi um segundo programa baseado nessa suposição. Dê uma olhada nisso Vamos começar com os primeiros métodos de medição medem A. Minha matriz de mordidas está aqui em cima, Andi. Vou fingir que estes são os dados da imagem carregados nesta matriz. Eu perco a matriz em convertidos, cada pixel em escala de cinza, usando a fórmula familiar. Então este culto usa indexação de matriz tradicional para converter cada pixel. Agora aqui está o mesmo código reescrito usando ponteiros. Eu usei as palavras-chave fixas para beliscar a matriz na memória, e então eu obter o endereço fora dos primeiros elementos na matriz com o operador de 1%, e eu atribui esse endereço para o ponteiro de mordida na variável P. O resto do casaco é exatamente o mesmo que os códigos baseados em array. Eu olho sobre cada pixel com esta variável por e usado ponteiro indexação para ler e escrever cada um por seu valor. E, finalmente, aqui está outro ponto de métodos baseados. Mas em vez disso, usando indexação de acesso do dedo do pé, cada pixel, eu simplesmente avançar o ponteiro em si. Cada loop interacional só lê os vermelhos verdes em valores azuis em, modifica-os em escala de cinza. Então eu avancei o ponteiro por três mordidas para acessar o próximo pixel, então ainda há alguma indexação neste método, mas apenas por 01 ou duas mordidas. Os principais métodos aqui em baixo chama todos os três métodos para tamanhos de imagem variados. Eu meço o desempenho para imagens simuladas de 512 até 4096 pixels de largura em passos fora 100 28. Agora, como mencionei antes, o software de captura de tela distorce minhas medidas, então preparei os resultados com antecedência. Aqui estão os resultados. Como você pode ver, não há muita diferença entre Medida A Medida B, usando uma matriz de blight ou compra ponteiro com indexação, tem desempenho mais ou menos igual. Mas olhe para a Medida C consistentemente mais rápido do que os outros dois em 25% em média. Então, por que a medida é ver tão mais rápido? Para descobrir, precisamos dar uma olhada no código intermediário. Então deixe-me verificar se o projeto está em modos de depuração. Eu vou colocar um ponto de ruptura nos métodos principais, em seguida, em torno do programa em mudar para a vista de desmontagem. Então, aqui vamos nós. Vou começar com a medida, uma mensagem que usa um regular gerenciado pelo tary. O código que modifica os pixels está aqui. Você pode ver. É o familiar três instruções de carga e, em seguida, uma instrução de elemento de armazenamento para modificar os elementos de matriz. A matriz de imagens está no local zero. A variável de índice I está no local para, como o valor a ser escrito está no local três. Já vimos isso muitas vezes. Por agora. Esta linha de códigos modifica o próximo valor de cor. É quase o mesmo revestimento, mas a variável I é incriminada por um com estes três locais de carga para carregar Constant um em adicionar instruções na terceira linha de códigos é quase o mesmo. Mas agora a variável I incrementa em dois. E, finalmente, aqui, a variável I é incriminada por três com estes quatro locais de carga baixa constante constante em instruções de localização da loja. Então, como fica esse casaco quando estamos usando ponteiros? Deixe-me rolar para baixo até a medida, ser métodos e encontrar as linhas correspondentes de revestimento Primeiro, o ponteiro P é carregado com este local de carga para instrução, em seguida, a variável. Eu é carregado com esta nota Localização três instruções sobre contas são adicionados juntos para obter o endereço de memória para escrever. Finalmente, o valor a ser escrito é carregado com este local de carregamento para instrução. A loja final em instrução direta executa o direito real à memória. Você vê que esta seqüência off instruções é quase o mesmo, exceto que há um extra na instrução para calcular o endereço de memória e que o valor é escrito com um armazenamento em instrução direta em vez de uma instrução de elemento de armazenamento . As próximas duas linhas são quase as mesmas, exceto para o extra adiciona instruções para adicionar um ou dois para a variável I. Agora vamos dar uma olhada. Isso é uma Medida C. Escrever para a memória é apenas estas três instruções adora localização. Três. Para carregar os pontos são localização de carga cinco a mínimos. O valor em escala de cinza Andi armazena indireto para gravar na memória. As próximas linhas têm essas duas instruções extras carregar constante um e adiciona ao incremento nomeado por um e carregar constância também, e adicionar para incrementar o ponteiro por dois. Finalmente, o ponteiro é incrementá-lo em três com este frio aqui, que é idêntico ao culto para incriminar a variável I nos outros dois métodos. Então, por que esse resfriado é mais rápido? Bem, para começar, este casaco tem menos instruções de linguagem intermediária do que os outros dois métodos medida ser necessário. Cinco em operações para modificar a matriz onde este culto só precisa adicionar operações e medir uma necessidade. Três instruções de localização de carga antes de chamar elementos da loja onde este revestimento só precisa de instruções. Além disso, o ponto Tradições são muito menores em medida ser. Temos que adicionar a variável I ao ponto de P para obter o endereço de memória como o fim fora do loop, Eu cresce para valores muito grandes até 48 milhões. Compare isso com métodos. Veja onde está o ponteiro. Apenas incremento é de 12 ou três mordidas. Adições de número pequeno são muito mais eficientes do que as adições de grande número porque o uso de CP tem instruções especializadas para incrementar o valor por um. Então, o que podemos dizer? Em conclusão? Quando está usando ponteiros valets Escolha. Vimos que conseguiu um aumento no ponto de índice de operações realizadas igualmente em ponto net. Mas para valores de índice muito grandes, é muito mais eficiente para incrementos nomeação e ler e gravar dados perto do ponto ou endereço de memória correntes. E então este é o principal take away para esta palestra. Normalmente, você não deve usar ponteiros em C afiado porque os ganhos de desempenho simplesmente não valem a pena . Ponteiros de índice e matrizes unidimensionais regulares têm desempenho igual. Para evitar ter que usar casaco inseguro, você deve escolher uma corrida. Como é em cenários específicos? O ponto de algoritmo baseado pode superar um algoritmo baseado em matriz em até 25% para esses cenários, as seguintes condições precisam ser atendidas. Primeiro, você está lendo e gravando um bloco grande fora de dados de pelo menos 10 megabytes de tamanho ou maior. Em segundo lugar, o algoritmo verifica cada mordida sequencialmente ou salta através dos dados de uma maneira previsível e espadas durante cada loop. Somente os dados de iteração muito próximos do endereço do ponteiro atual precisam ser lidos ou gravados. Se o seu algoritmo aderir a estas condições e você não se importa de produzir uma montagem insegura , sinta-se à vontade para usar ponteiros e pegar os 25% extras em desempenho 23. recap do curso: Parabéns. Você completou todo o curso. Agora você é um otimizador de código C afiado certificado. Eu mostrei a você cerca de 25 tipos diferentes de otimização de chamadas. Como usá-los em sua própria fábrica de pelagem, quais melhorias de desempenho específicas que eles lhe darão. A otimização é foram espalhados por três seções. Tivemos a otimização básica, a fruta baixa pendurada onde uma simples edição fora do seu casaco poderia resultar em uma enorme 100 dobras Melhoria do desempenho. . Temos as organizações intermédias que eram mais complexas, chamadas de direitos que oferecem apenas uma pequena e média melhoria em. Tivemos a otimização avançada do núcleo duro, onde usamos vários recursos exóticos fora da estrutura Net para aumentar o desempenho. Essa otimização tem dado a você uma rica caixa de ferramentas fora do conhecimento e idéias que você pode usar ao escrever seu próprio casaco ou quando você está colaborando em uma equipe de desenvolvimento, especialmente se você estiver trabalhando em códigos de missão crítica onde o desempenho superior é É crucial. E se você descobriu alguma nova técnica de desempenho interessante de sua própria polícia, compartilhá-los na discussão curso para ele, para todos nós para desfrutar. Espero que você aproveite o curso sobre aprendeu algumas novas técnicas úteis que você pode aplicar em sua carreira desenvolvimentos de software Agora vá e construa grandes coisas