Como otimizar o uso de memória em C# | Mark Farragher | Skillshare

Velocidade de reprodução


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

Como otimizar o uso de memória em C#

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

17 aulas (2 h 59 min)
    • 1. Otimize o uso de memória em C#

      2:37
    • 2. Introdução à introdução à . Gestão de memória

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

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

      4:59
    • 5. Como os tipos de valor armazenadas na memória?

      5:42
    • 6. Como os tipos de referência armazenados na memória?

      5:29
    • 7. O que é boxe e is

      6:03
    • 8. Como funciona a coleção de lixo em funcionamento de coleta de de lixo de NET? Dorm

      16:46
    • 9. Dica 1: otima seu código para coleção de lixo

      18:20
    • 10. Dica 2: evitar os finalizações do curso

      17:11
    • 11. Dica #3: use o padrão de afastamento

      16:34
    • 12. Dica #4: evite boxe e and

      9:51
    • 13. Dica #5: não adicione muitas cadeias

      16:08
    • 14. Dica 6: use as estruturas em em vez de aulas

      17:55
    • 15. Dica #7: sempre alocar coleções

      12:30
    • 16. Dica #8: não materialize as expressões LINQ

      18:04
    • 17. Receita do 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.

63

Estudantes

--

Sobre este curso

3bb6dc07

Os computadores modernos têm muitas memória. Mas é muito fácil gravar em tudo em segundos, se o código não não é eficiente sobre a alocação e uso de memória.

Você quer que um erro simples pode fazer seu código afetar 1600 ou mais de memória do que não for necessário?

Não seja "esse desenvolver que continua com o servidor de desenvolvimento com uma exceção OutOfMemory!

E você certamente não deve ser responsável por inflar o orçamento do hardware. Você pode imaginar ter que explicar à sua equipe que não é suficiente para executar seu código no servidor de produção?

Deixe que o ajudar.

Não precisa ser assim. Se você tiver uma boa compreensão do processo de coleta de lixo e seguir algumas melhores práticas simples, você pode reduzir dramaticamente a gravação do seu código.

Som bom?

Nos últimos 10 anos aprendi os segredos da coleção de lixo em em ou de coleção. NET, e no curso, vou compartilhar todas as com você.

Em uma série de aulas short vou dar uma olhada detalhada do processo de coleta de lixo sobre lixo. Vou mostrar todos todos os problemas de alocação de memória que você pode esperar ao escrever o código C# ao a partir de boxe inesperado, duplicação de strings, resizing, de cores, resizing, a coleção e muito mais. Vou ensinar estratégias rápidas e fáceis para resolver esses problemas.

Ao final do curso, você vai dominar o coletor do lixo.

Por que você deve fazer este curso?

Você deve fazer este curso, se for um desenvolvedor de C# iniciante ou intermediário e quer levar suas habilidades para o próximo nível. A coleção de lixo e a gestão de memória ou a gestão de coleção e de memória ou que pode parecer de ação, mas todas as minhas aulas são fáceis de seguir e eu explicar todos os tópicos com o código de código de e muitos diagramas instrutivos Você não terá problemas para seguir ao acompanhar.

Ou talvez você esteja trabalhando em uma seção crítica de código em um projeto C# e precisa ter para garantir que seu uso de memória seja mais eficiente possível? As dicas e truques neste curso vai ajudar você imensamente.

Ou talvez você esteja se preparando para uma entrevista de trabalho relacionada para C# de CN#? Este curso vai dar uma base excelente para responder a qualquer questão que que de nos em que de que a que de de que possa de sua de ação.

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. Otimize o uso de memória em 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 fundação fora do tempo de execução dot net. Então eu vou te dar um curso intensivo na pilha da pilha. 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, estaremos olhando para otimização de memória. Então eu vou ensinar exatamente como o coletor de lixo funciona em quais suposições ele faz sobre objetos, tamanho e objeto. Lifetime sobre como você pode otimizar seu casaco jogando nessas suposições, nós também vamos olhar para alguns tópicos exóticos, como horas finalizadas. Vamos olhar para o padrão de eliminação. Vamos olhar para vários pedaços de frio? Isso é que usam muita memória lá, muito ganancioso na alocação de memória, então eles acionam o coletor de lixo muito, e isso é novamente retarda seu casaco para que você aprenda a escrever código que torna eficiente usar fora da memória. 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 desempenho C Sharp Architects? 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 que querem aprender a escrever rápido e eficiente casaco C afiado para preparar sua carreira 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 à introdução à . Gestão de memória: Vamos dar uma olhada em um programa típico à beira-mar. O programa consistirá em classes contendo campos e métodos. Um método típico começará com objetos de valor da música sendo declarada em inicializada. Depois há códigos para executar algum tipo de ação. E finalmente, a mensagem termina. As variáveis nos métodos são alocados na pilha e sempre que você vê as novas palavras-chave em objetos contra alocados no heap. Mas aqui vai uma pergunta para você. Qual é o oposto? Off New, Quais palavras-chave exclui em objetos Algumas linguagens realmente têm uma palavra-chave especial chamada delete, que é o oposto de novo em exclui explicitamente um objeto do heap, mas o framework dot net não tê-lo. Há novas palavras-chave, mas não há cortesia. Exclui palavras-chave. Como é que isso é possível? A resposta é que há um processo especial chamado coletor de lixo que está continuamente excluindo objetos que não são mais usados pelo seu casaco. Você pode estar se perguntando por que a Microsoft escolheu implementar uma solução tão complicada. Por que não simplesmente adicionou as palavras-chave de leads para a linguagem nítida C e evita a necessidade de um coletor de lixo completamente? A razão, eles adicionaram o coletor de lixo é muito simples em idiomas que dependem das palavras-chave exclui . Os erros de programação mais comuns são devidos ao inquérito. Juice off New e Elise localizando um objeto com novo, mas esquecendo de excluí-lo. Isso é chamado de vazamento de memória, usando um objeto depois que ele já está sendo excluído. Isso causará memória, corrupção e exclusão de um objeto que não está sendo alocado. Isso faz com que no erro de memória adicionando um coletor de lixo para o framework dot net, Microsoft eliminou completamente esses erros de programação. É muito difícil, não impossível. Lembrem-se que vocês dois criam a liga da memória em C afiada ou corrompida memória do programa. Mas um coletor de lixo também tem desvantagens porque metade do gerenciamento de memória agora é totalmente automático. Os desenvolvedores tendem a perder de vista as pegadas de memória de seus códigos, e isso pode levar a códigos que funcionam da maneira que deveriam, mas alocam milhares de vezes mais memória do que realmente precisa. Vou mostrar-lhe como medir as alocações de memória dos seus códigos. Vou te ensinar exatamente como o coletor de lixo funciona, e vou compartilhar vários truques simples com você que o ajudarão a reduzir drasticamente as pegadas de memória de seus próprios códigos. 3. O que é memória de pilha?: 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 é memória de: 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 de redes internas 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 esperados. Quatro enterraram seus parâmetros para traçar 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 draw square configura uma matriz de linha com quatro objetos correspondentes aos quatro lados fora do quadrado e, em seguida, chama Drop all 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 um. Então, agora o que acontece quando o método do polígono de desenho 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. A matriz 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 importante tirar o quadro dot net sempre adiará a limpeza dos objetos referenciados, e isso ocorre porque a limpeza do heap leva muito tempo, 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 . Esse processo de limpeza é chamado de coleta de lixo, e acontece periodicamente em segundo plano quando a estrutura inicia 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. 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 ela se referirá a um objeto no heap quando parâmetros e variáveis locais na pilha saírem do escopo. Os objetos correspondentes da pilha eu não estou destruído. Eles continuam a existir em um estado referenciado em D. A estrutura de rede adia a limpeza dos objetos referenciados no heap pelo maior tempo 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. Como os tipos de valor armazenadas na memória?: na palestra anterior, aprendemos sobre essa pilha na pilha agora. Anteriormente, quando eu falei sobre essa pilha, eu mostrei algum código com a bagunça da linha da gaveta. É 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 e o valor da variável distorcem 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 falei sobre essa pilha, mencionei todos os tipos de dados armazenados no estoque. 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 em desenhado com o valor off 1234 ele seria armazenado na pilha como este. Você vê que o tipo e o valor armazenados juntos na pilha agora manter 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 Sim, deixe-me demonstrar. Anteriormente, quando falei sobre o heap, mencionei que tipo de dados são armazenados no heap. Você se lembra o que ele faz? Ele waas todas as instâncias de objetos criados 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 é que o quadro dot net interpreta bem a situação? 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 junto com o valor de tipo. Tipos podem existir na pilha em e acreditar que os tipos de valor 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. Como os tipos de referência armazenados na memória?: 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 is: Nesta palestra, vou mostrar-vos um mistério. Dê uma olhada neste casaco. 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 e eu atribuo a B em C afiado todas as vezes herdar de objetos, incluindo inteiros, para que você possa colocar basicamente tudo em uma variável de tipo de objeto. Mas espere Inter-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, portanto ela existe na pilha no dia. É um tipo de valor, então seu valor também é iniciar na pilha. 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 compilar este programa em 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 thes objetos no para que a variável B possa seguida, se referir ao seu. Todo esse processo se chama 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 ao seu boxe é bom porque ele meio que desfoca a linha entre tempos de valor e tipos de referência. Mas o boxe também pode ser uma dor porque 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 tipo inteiro e atribuir o valor de objetos para o seu 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 desboxe”. Começamos a partir da situação da caixa com o inteiro no Agora. Então isso acontece. Unboxing desempacota o inteiro no calor 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. Então, 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 tipo de valor a um parâmetro variável de campo ou propriedade off type objetos. Unboxing é o processo inverso. Objetos fora do heap são descompactados no valor. Tipos dentro são copiados de volta para a pilha. Aprender. boxe acontece sempre que você não tem valor de objeto, e você joga um tipo de valor. 8. Como funciona a coleção de lixo em funcionamento de coleta de de lixo de NET? Dorm: 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 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 ativados. Esses 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 chuta no coletor de lixo da rede da porta é um coletor de marca e varredura, que significa que 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 calor, então neste exemplo, esse seria o próprio array. Nos 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 o palco de varredura todos os objetos que têm nots e sendo marcado no estágio anterior R D referência. E nesta fase eles são alocados a partir da pilha. Então, neste exemplo, os objetos dois e três não têm bean marcado em, então 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 no heap 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 objetos referenciados 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 está geracional ligado. Ele 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, então o layout de memória ficaria 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 e todos os objetos que sobreviveram à varredura se movem para a Geração um. Então, após um ciclo, o layout de memória se parece com esta matriz Z em e Objetos 01 e quatro sobreviveram à varredura e agora estão na primeira geração. Agora imagine que o programa continua neste ponto. Tia 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. O array reside 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 passam para geração para no novo objeto na Geração zero move para a geração um. Se o programa continua e coloca um novo objeto seis em um raro elementos três, ele iria novamente para a geração zero. Nós agora temos uma matriz em geração para referir-se a objetos na geração 01 tem que novamente provar a violência, então você pode estar se perguntando neste momento 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 gerações-chave, uma como a 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, modo que o coletor de lixo tem menos trabalho a fazer. Segundo, objetos de vida longa que sobrevivem em geração para eu não sou verificado com muita frequência, que é exatamente o que queremos. O coletor de lixo geracional é um belo, algoritmos de alto desempenho, mas tem uma desvantagem importante. É ineficiente como o processamento de grandes, objetos de longa vida consideram 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 tez 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, esta solução para este problema é ter dois heaps separados, um para pequenos objetos em um. Para objetos grandes, o design parece algo como essa indignação que são muito quadris. A pequena pilha de objetos, que funciona com as três gerações que discutimos antes das mãos a grande cultura manter. A coisa especial sobre a pilha de objetos grande é 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 grande objetivo é 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. Todos os objetos em 85 kilobytes foram maiores vai diretamente para a pilha de objetos grande. Qualquer objeto menor que esse limite vai para o heap de objeto 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 cão redes coletor de lixo é uma qualidade de lixo geracional ou que usa um ciclo compacto de varredura marca , e tem pilhas separadas para objetos grandes e pequenos objetos. Se você pensar sobre isso, o ponto Net coleta de lixo faz algumas suposições muito específicas sobre objetos e vidas. Primeiro, ele assume que objetos serão folhas curtas 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 no progresso em direção à 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 todos os objetos menores que 85 kilobytes devem ser vidas curtas para todos os objetos maiores que 85 kilobytes devem ser longa vida e 9. Dica 1: otima seu código para coleção de lixo: nesta palestra vamos olhar para vários otimização é fazer o coletor de lixo correr 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 amontoam os pequenos objetos que ele usa. Três gerações, todos os novos objetos são alocados na geração zero mãos. progresso em direção à geração zero é coletado com muita frequência. Gerações um mãos muito menos então 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 é com a concatenação de string Dentro da mensagem anexa, você se lembrará de que strings são imutáveis e, portanto, a mensagem de duas strings na adição cria objetos de string extras no heap para cada loop -lo oração. O frio na parte inferior evita esse 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 em. 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 caixa 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. O código na parte inferior evita esse problema. Meu primeiro, tornando os objetos não estáticos lá estão localizando é pouco antes de usar e finalmente definindo a referência do objeto para saber logo após o uso para sinalizar para o coletor de lixo que foram feitos em que os objetos estão prontos para ser recolhidos. 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. Saia 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. Os quadrantes superior esquerdo e inferior direito estão em desacordo com as suposições do coletor de lixo . Se o seu código contém muitos objetos desses quadrantes, você está efetivamente trabalhando contra as suposições fora do coletor de lixo que o 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 e, em seguida, usa 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 ativada, em seguida, reutilizar a lista para o segundo método, chamar a nova camada. Os mesmos objetos de lista de matriz é usado repetidamente. vida é aumentada sobre os objetos efetivamente torna-se longa vida. Essa alteração melhora o desempenho em 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 pares de objetos, libras cada cabelo contém dois inteiros. 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 heap de objeto grande e é supõe 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 uma referência toe cada aisam, todos esses pais nunca d referência, e eles acabarão por se mover para uma geração. Para a solução é usar outro popular, re estratégia de fatoração em vez disso, tendo uma lista com dois inteiros em cada elemento lista, dividimos a lista de partes em dois inter separados gera porque um inteiro é um valor , ele será armazenado com o array. Então agora só temos dois aumentos maiores na pilha de objetos grandes 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. Um divide o objeto de partes em subobjetos, cada um menor que 85 kilobytes ou dois. Reduza 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. A Andi. 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 um 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 no heap de objeto grande. 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 até 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ê uma voz, a migração da geração zero para geração para inteiramente. 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. Terceiro é reutilizar 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 grande e curto ou pequeno em elevador longo , você pode querer re fator-los para grandes assuntos brevemente. Você pode aumentar a vida útil ou diminuir o tamanho do objeto e quatro objetos de elevação pequenos e longos. Você pode diminuir o sinal de vida ou aumentar o tamanho. Todas essas mudanças irão beneficiar o desempenho do seu casaco. 10. Dica 2: evitar os finalizações do curso: nesta palestra, eu gostaria de falar sobre Finalize er. Um izer final é um classmethod que é executado automaticamente pouco antes em objetos fica limpa pelo coletor de lixo. Finalizar. ER também são às vezes chamados de estruturas D para significar que eles são os opostos fora construtores, enquanto um er construto inicializar é e objetos em prepara-lo para o primeiro uso uma estrutura D faz a tia oposta limpa qualquer persistente. O recurso é para que os objetos possam ser descartados. Finalizar er são muito úteis se você alocar recurso escasso está em seu objeto. Por exemplo, no identificador de arquivo do sistema operacional, há apenas um número limitado fora dos identificadores de arquivo disponíveis, por isso é muito importante liberar o identificador quando você terminar com o objeto. Claro, você poderia implementar uma mensagem próxima. Mas e se alguém esquecer de ligar? É mais seguro liberar o identificador de arquivo em um Isar final. Então é isso que é garantido para ser liberado quando o objeto é limpo em casaco que seria algo parecido com isso. Você pode ver a declaração de classe na parte superior do construtor. Essa inicialização é o objeto. Andi, abaixo do construtor é um método de aparência estranha que começa com um caractere til. Este é o último Isar em. Ele será chamado pelo coletor de lixo pouco antes do objeto ser limpo. Nas palestras anteriores, mostrei como funciona o coletor de lixo. Você pode se lembrar deste leve com as três gerações na matriz fora de objetos se movendo através deles. Vamos dar outra olhada no processo de coleta. Neste momento, vamos supor que cada elemento de matriz tem um finalizado. Então, se revisitarmos esse cenário com os elementos dois e três definidos para saber, então os layouts de memória seriam assim. Tudo é o mesmo de antes, com todos os objetos residindo na geração zero gerações. Um para estão vazios. O primeiro ciclo de coleta faz uma marca na varredura em todos os objetos que sobrevivem esta semana se movem para a Geração um. Então, após o primeiro ciclo, o layout da memória se parece com isso. A matriz dos objetos 01 e quatro sobreviveram a este doce e estão agora na primeira geração. Mas aqui está a mudança. Objetos dois e três não são limpos. Eles não podem ser porque eles têm finalizar Er, que ainda precisa ser executado, Então o coletor de lixo também move objetos dois e três para a geração um e adiciona-los a um Que finalização especial após o ciclo de coleta completa um ameaça separada chamada Threads de Finalização é ativada. Andi vai trabalhar, executando o er finalize fora de todos os objetos na finalização Que Vamos dar uma olhada em algum casaco real que eu escrevi. Eu fiz um pequeno programa que aloca objetos com Finalize er para que possamos ver foi. É assim que as chamadas dão uma olhada neste código. Aqui está a mensagem principal do programa que aloca repetidamente objetos em um loop. Cada objeto recebe um número de índice exclusivo para que possamos rastreá-los individualmente. Quando pressiono uma tecla, o loop termina. O aplicativo escreve um texto no console que está terminando no programa principal. Threads termina, e aqui está a declaração de classe fora da classe My Object. É uma classe muito simples com apenas um único enterrado seus campos Onda, um construtor para inicializar o inteiro. O construtor tem uma linha extra aqui que os direitos para o console em que a geração de coleta de lixo , os objetos resorts. Andi aqui é a estrutura Classe D ou finalizar er. Ele dorme por 500 milissegundos e, em seguida, grava a geração atual no número de índice no console. Certo, vamos executar o programa. Aqui vamos nós. Tudo parece estar trabalhando muito fora de objetos sendo construídos na geração zero, assim como seria de esperar. Agora vou pressionar uma tecla. Você pode ver que as Urgências Finalizadas estão sendo chamadas a cada 500 milissegundos. A principal ameaça do programa terminou, mas a finalização Q ainda está cheia de objetos, então os threads de finalização continuam acontecendo, finaliza objetos um por um até que o processo finalmente termine. Mas olha para isto primeiro. Os números de índice estão em ordem aleatória, então sabemos que o coletor de lixo está processando o er Finalizar em segundos de ordem aleatória . Todos os objetos são finalizados na Geração 1 e espadas. Depois de pressionar uma tecla, o aplicativo finaliza oito objetos e termina o que acontece com os milhões de outros objetos. Então agora você deve ter um palpite que finalizar er são coisas complicadas. Os sentidos são fáceis de errar. Vamos analisar as observações. Um por um, vou começar com a borda aleatória fora dos números de índice. Então vimos que o coletor de lixo processa objetos em uma ordem completamente aleatória. Isso significa que seus códigos Isar finais não podem fazer quaisquer suposições sobre outros objetos que ainda estão vivos. Qualquer referência que seus objetos possam conter já pode estar finalizada. Isso leva a uma regra muito importante Ao escrever finalizar ER um Isar final só deve processar seus próprios objetos e nunca outros objetos de referência. Aqui está um bom exemplo. Dê uma olhada neste código. Se o escritor Stream for finalizado primeiro, tudo ficará bem. Os dados são liberados para o disco em. Em seguida, o arquivo fecha. Mas se os objetos são finalizados de outra forma rodadas, em seguida, o gravador de fluxo tentará gravar dados em um arquivo fechado no aplicativo vai esmagar. Então este casaco iria realmente bater 50% de desconto no tempo, dependendo da ordem fora do er finalize. Mas, na realidade, isso não acontece. A Microsoft viu esse problema chegando e decidiu não implementar um er finalize na classe de escritor de fluxo . Então, se você esquecer de fechar o riser de fluxo, seu código saberá falha. Mas a string também não será gravada no disco. Você perderá dados, portanto, em resumo, você não pode fazer quaisquer suposições sobre a ordem em que seus objetos serão finalizados e você nunca deve tentar usar um objeto referenciado em seu Isar final porque ele pode já ser finalizado. Tudo bem, a segunda coisa que observamos no código é que todos os objetos estão finalizados na Geração 1. Isso faz todo o sentido quando você pensa sobre isso. O coletor de lixo marca todos os objetos de vida na geração zero. Andi os move para a primeira geração. Todos os objetos referenciados D com Finalize ER também são movidos para Geração um para segurança e adicioná-lo à finalização que Os segmentos de finalização processa os objetos um por um, chamando cada er finalize, removendo os objetos da fila. Depois de cada ER finalizar foi executado. Os objetos são finalmente limpos no próximo ciclo de coleta. Vimos em uma palestra anterior que o coletor de lixo é otimizado para pequenos objetos de vida curta com elevação curta. Quero dizer, no objeto, cuja vida inteira se encaixa na Geração Zero. Então aqui está uma grande desvantagem fora objetos com Finalize er. Sua vida sempre vai abranger pelo menos a geração zero. No primeiro dia, leva pelo menos dois ciclos de coleta para que os objetos sejam finalmente limpos, com o Isar final sendo executado entre as coleções. E se houver muitos objetos na fila. Pode até levar até geração para antes que o objeto finalmente seja finalizado. Se você tem muitos pequenos objetos de elevação curtos com Finalize ER, você realmente vai contra o lixo coleta ou porque você está estendendo sua vida em geração um e, assim, dobrando o número fora das operações de cópia de memória necessário para marcar varredura e compacto profundo. Outra desvantagem fora finalizar er é que qualquer objeto que você referência também vai acabar em geração um. Então, se você implementar uma classe de coleção com o Isar final, isso significa que a coleção mais todos os itens na coleção acaba na primeira geração antes de ser coletado. Isso me leva a uma importante regra de otimização. Você nunca deve acrescentar. Finalizar er para objetos nas raízes, muitas vezes objeto gráfico apenas dois objetos na borda. Finalmente, vamos olhar para a última observação do revestimento. Vimos que depois que pressionei a tecla, apenas oito objetos foram finalizados antes do fim do aplicativo. O que aconteceu lá? A resposta é realmente muito simples. Quando um processo está saindo, o tempo de execução da rede DOT dá à ameaça de finalização um máximo de quatro segundos para processar quaisquer objetos restantes no que de finalização após esse prazo. Todo o processo termina em todos os objetos restantes no Q r D alocados sem chamar seu Isar final sobre isso me leva ao ponto final sobre Finalizar er. Eles não são garantidos para ser chamado se o processo host está saindo qualquer coisa restante na finalização Que Depois de quatro segundos, ele é descartado. Ok, então vamos resumir as desvantagens de Finalizar er que vimos até agora. Finalizar er são esfria em uma ordem aleatória. Objetos com Finalizar er sempre acabam em geração um em, às vezes até mesmo em geração para quando eu processo termina, alguns finalizar er pode não ser chamado, e aqui está um resumo off. Os tópicos que abordamos nesta palestra finalizam ER são métodos de classe que são chamadas quando objetos estão prestes a ser limpos pelo coletor de lixo. Muitos pequenos objetos de vida curta com Finalize Er são ruins porque o Isar final estende sua vida em uma geração. Você só deve adicionar, finalizar er para objetos na borda, muitas vezes gráfico objeto. Finalizar. Er nunca deve fazer referência a outros objetos. Finalizar er deve ser extremamente curto. As mãos correm muito rapidamente 11. Dica #3: use o padrão de afastamento: o coletor de lixo em redes de ponto é muito útil na medida em que ele constantemente verifica o heap para objetos referenciados em D e os limpa em ciclos de coleta discretos. À medida que os programadores enfraquecem, basta alocar objetos quando precisamos deles e esquecê-los. Quando terminarmos, o coletor de lixo vai nos seguir ao redor, por assim dizer ons limpar nossa bagunça quando no objeto precisa alocar alguns tipos de recursos escassos , Digamos, um identificador de arquivo nativo ou um contexto do dispositivo gráfico ou um bloco fora da memória nativa, Em seguida, o lugar perfeito para fazê-lo é no construtor. No momento em que o objeto estiver pronto para uso, o recurso também estará pronto e disponível. Por outro lado, o melhor lugar para liberar o recurso é no Isar final. Os métodos Isar finais é chamado automaticamente pelo coletor de lixo pouco antes que os objetos obtém o alocado. Fazendo, assim garante que o recurso é liberado no momento exato em que o coletor de lixo está prestes a de alocar o objeto. No entanto, essa abordagem tem um grande problema. O tempo entre a construção na finalização pode ser enorme. O coletor de lixo é lento em tenta executar o mínimo possível. Na verdade, se houver memória suficiente do sistema disponível, o coletor de lixo pode não ser executado. Isso é tudo, preferindo deixar a memória encher lentamente ao longo de períodos fora dias ou semanas, mantendo um recurso escasso alocado por dias. Ele é uma prática terrível que mata toda a escalabilidade. O que piora é que o objeto provavelmente não está mais sendo usado e já venceu o referenciado. Então, como resolvemos esse problema? Esta solução é usar o padrão de eliminação. Este padrão de design apresenta uma mensagem descartada para liberar explicitamente. O recurso é, vamos dar uma olhada em um resfriado. Aqui está um pequeno programa que escrevi dois demonstra as vantagens fora do padrão descartado. Vamos começar com a mensagem principal do programa aqui. Eu tenho duas declarações de campos estáticos na parte superior, objetos finalizados que irá manter o número total fora de objetos finalizados e tempo total, que irá manter o tempo de vida total fora. Todos os objetos se combinam em milissegundos. Então aqui neste loop, eu aloca 500 milhares de objetos em uma chamada lá para métodos de trabalho para simular alguns tipos fora do trabalho que está sendo feito. Então, quando o loop termina, eu mostro o número total de objetos finalizados no número médio de milissegundos em que o objeto estava vivo. Vamos dar uma olhada na classe sem disposição. É uma classe muito simples que aloca e inicia um cronômetro em É construtor. A mensagem do trabalho do é um loop simples que faz algum tipo de cálculo fora para simular o trabalho está sendo feito no Isar final aqui em baixo pára o cronômetro e atualiza os dois campos estáticos no programa principal. Observe que eu uso a classe interbloqueada para atualizar os campos estáticos de uma maneira segura contra ameaças porque pode haver mais de um threads tentando atualizar os campos ao mesmo tempo. A turma intertravada certificar-se-á de que é tudo. Threads são executados em sequência e nenhuma condição apagada pode uma cura. Ok, deixe-me executar o programa. Você pode ver os resultados. Aqui vamos nós. Aqui estão os resultados. O programa conseguiu finalizar 455.000 objetos, que significa que os restantes 45.000 objetos ainda estão no heap na geração zero esperando para serem coletados. A vida útil média geralmente objeto é 736 milissegundos. Portanto, este é o tempo médio de folga entre a construção na finalização. Então aqui está o nosso problema, estamos fingindo que são objetos, aloca algum tipo de recurso escasso. Mas essas fontes re estão sendo mantidos por 736 milissegundos antes de serem liberados ums. qualquer momento, existem 45.000 objetos de referência não coletados na pilha esperando para serem coletados bundas ainda segurando seu recurso. Isso vai ser terrível para escalabilidade. Podemos corrigir esse problema usando o padrão descartar. Dê uma olhada nesta classe aqui chamado com Dispose em que eu implementei o padrão . Você pode ver que o construtor para o fazer métodos de trabalho são exatamente os mesmos. Mas olhe para esta interface aqui. O padrão descartado requer que você implemente a interface descartável I. O inter fence define um único método de descarte no qual você deve liberar explicitamente qualquer alocado. O recurso escasso é que eu implementei meus métodos de disposição aqui. Minha mensagem simplesmente chama outros métodos de eliminação virtual protegidos. Aqui em baixo está o Isar final, que também chama os métodos virtuais protegidos descartados. Você pode ver que o Isar final fornece um argumento falso sobre os métodos públicos descartados, fornece um verdadeiro argumentos. Então, dentro dos métodos protegidos descartados eu não posso saber distinguir se o código está sendo resfriado do Isar final ou dos métodos descartados do público. Isso é muito útil porque você pode se lembrar da palestra sobre Finalizar er que há muitas coisas que você não tem permissão para fazer dentro do Isar final, como, por exemplo, acessar referências a outros objetos. Então, se este argumento booleano é falso, eu sei que o código está sendo executado na ameaça final Isar em. Não consigo acessar outros objetos. Mas se o argumento for verdadeiro, então esse método foi chamado a partir de códigos de usuário e tudo está bem. Posso fazer o que quiser. Há também um Booleano descartado aqui que garante que o casaco descartado é executado apenas uma vez . Então, se por alguma razão alguém continua chamando o público métodos dispostos repetidamente este casaco só vai executar uma vez. Finalmente, gostaria de mostrar mais um truque legal. Imagine por um segundo que os métodos descartados foram chamadas sobre os objetos liberados. Seu recurso alocado é então agora não há necessidade para o Isar final para executar. Isso é tudo porque não há mais nada a fazer. Claro, o Isar final não faria nada porque a variável descartada já está definida como true. Mas lembre-se, um Isar final vai estender o tempo de vida de qualquer objeto para a primeira geração. Isso pressionará o coletor de lixo sem motivo. Felizmente, existe uma solução muito legal. Você pode realmente desligar o Isar final para qualquer objeto. Esta linha de códigos aqui desativará o Isar final se os métodos descartar tiverem sido executados corretamente sem erros. Então, depois de chamar descartado, os objetos efetivamente não tem mais um Isar final, o coletor de lixo pode ser descartado com segurança no próximo ciclo eleitoral e o objeto nunca será promovido para a primeira geração. Ok, então eu vou fazer algumas alterações no programa para usar os novos objetos que implementa o padrão descartado. Primeiro, vou mostrar-lhe o que acontece quando eu simplesmente mudar o tipo de objetos que estão sendo criados em. Não faça mais nada. Deixe-me executar o programa. Aqui vamos nós. E aqui estão os resultados praticamente iguais aos da execução anterior. 462.000 objetos sendo finalizados e, em média, tempo de vida fora 639 milissegundos. Eu agora tenho esta mensagem legal descartada, mas eu não estou realmente chamando isso. Assim, o recurso ainda é ser liberado no Isar final no recurso médio. A vida é praticamente a mesma de antes, então vamos consertar isso. Vou mudar a forma como os objetos são alocados dentro do loop. Vou usar a instrução usando que garante que os métodos descartar recebe chamadas no final fora do bloco usando. Então isso deve ser agora. Em cada novo arejamento de passo, o objeto é criado, usado e, em seguida, descartado imediatamente. Estou segurando o recurso é para o mais brevemente possível. Vou executar o programa de novo. Confira isso e aqui estão os resultados. O programa já descartou todos os 500.000 objetos porque o loop descarta cada objeto antes de iniciar a próxima iteração. No tempo de vida médio desligado, o recurso caiu para um impressionante 0,26 milissegundos. Isso é 245.000 vezes menos do que o código que não usou o padrão descartar. Você pode visualizar os resultados assim. Se este gráfico horizontal representa o tempo, podemos desenhar uma linha todo o caminho à esquerda quando o objeto é construído em outra linha todo o caminho à direita, onde o objeto é finalizado. Mas este ponto aqui é quando terminamos com o objeto em prestes a deixá-lo sair do escopo . É vital chamar explicitamente dispor neste ponto, porque caso contrário, estaríamos segurando o recurso é durante todo o tempo de folga entre a referência sobre a finalização, que é 245.000 vezes mais do que o tempo que realmente precisava do objeto em primeiro lugar. Aqui está um resumo sobre o que aprendemos nesta palestra. O padrão descartado fornece uma mensagem para liberar explicitamente recurso escasso é o uso instruções irá chamar automaticamente o método descartar no final do bloco usando . O padrão descartado reduz drasticamente o período de tempo em que o recurso é mantido por objetos. Chamar os métodos descartar suprime o Isar final, o que impede que a vida útil dos objetos se estenda para a geração um 12. Dica #4: evite boxe e and: Vou ensinar-te a melhorar as alocações de memória no teu casaco. Nesta palestra, vamos dar uma olhada nos boxe. Desboxe. Você pode estar se perguntando se boxe Tia Unboxing tem consequências negativas como, por exemplo, aumento da pressão sobre o coletor de lixo. Bem, vamos descobrir. Escrevi um programa para medir a diferença nos padrões de alocação de memória entre fragmentos de código que não usa boxe versus fragmentos de código que usa boxe. Aqui está a costa. Vou começar com códigos que não usam boxe. Então aqui está o método principal do programa, com um loop simples que chama um método de teste um milhão de vezes. Você pode ver que eu passo o índice nuclear atual para a mensagem de teste para cima. Aqui está a declaração de métodos de teste. A mensagem de teste, exceto nos parâmetros entrevistados, não faz absolutamente nada. Eu vou criar um projeto de lei liberado fora deste programa em mono desenvolver usando o menu construído . Aqui vamos nós. Ok, então agora eu tenho em executar arquivo herbal na pasta bin slash release fora dos meus projetos. O próximo passo é executar manualmente este programa na linha de comando em uso O incrível look profiler que está embutido na estrutura do modelo. Deixe-me mudar para a linha de comando agora para executar um programa mono. Tudo o que precisa fazer é chamar o gemido. Oh, execute herbal Andi como os primeiros argumentos fornece o nome de arquivo de ervas executar que você deseja executar para habilitar o Profiler. Tudo o que você precisa fazer é adicionar este parâmetro de configuração aqui O parâmetro profile habilita o look profiler na opção de relatório indica que eu não quero um arquivo de log no disco. Eu simplesmente quero ver o relatório de desempenho diretamente na linha de comando. Ok, então aqui vamos nós. Vou pedir o programa em “Este é o relatório de desempenho”. Vamos ver as seções uma a uma, parece que ele tem dados de versão. Estou usando a versão zero pontos quatro fora do profiler, a instrumentação de perfil introduzida em despesas extras fora 60 nanossegundos no meu casaco que não é essas mãos. Você pode ver que eu executar o programa em 4 de setembro de 2015. A seguir é a seção jit. O compilador just in time compila oito métodos em cerca de um kilobytes fora da linguagem de máquina nativa , e aqui as coisas ficam interessantes. O coletor de lixo não aqueceu precisamente. Ele moveu 21 objetos durante a compactação de heap. Hans realiza dois ciclos de coleta na Geração 1, que em média levou 1146 microssegundos cada. Você pode estar se perguntando por que não há coleções Geração zero. Bem, é porque o meu programa não atribui nada na pilha. Todos os objetos na memória são do próprio tempo de execução dot net, e eles foram criados antes mesmo do meu programa começar. É por isso que todos acabaram na Geração 1. O resumo de alocação mostra uma lista de todos os objetos alocados enquanto o programa estava em execução . A lista está cheia de objetos de limpeza, mas você pode ver inter jours aqui em baixo. Um desses dois é a nossa variável loop. Assim, no total, 33 objetos foram alocados, que ocupam 3496 mordidas de memória térmica. Ok, não, eu vou fazer uma mudança no programa, verificar as células deles. Eu vou mudar o tipo fora os argumentos fora dos métodos de teste para objetos. Então, agora, durante cada loop, é oração. O inteiro precisa ser encaixotado no heap, então esse é o tipo de objeto. Argumentos podem se referir a ele. Vamos ver que tipo de impacto essa alteração de código terá nas estatísticas de alocação de memória . Vou construir o programa novamente para atualizar o arquivo de ervas executar no disco. Em seguida, l alternar para a linha de comando em uma corrida. O programa com perfil de aparência novamente. Aqui vamos nós. Aqui estão os resultados. Confira as diferenças. O coletor de lixo agora executou cinco coleções de geração zero em coleções de geração 1 . O tempo médio por coleção é aproximadamente o mesmo, mas isso é mais do dobro do número de coleções que tínhamos antes. Mas a maior diferença está no resumo da alocação. Tenho agora um milhão de libras para introduzir alocados. Você está comprando 24 megabytes de memória? A pilha agora contém um milhão em 33 objetos abrangendo 24 megabytes de memória. Isso é muito interessante porque você provavelmente sabe que um inteiro tem apenas quatro bytes comprimento, mas um inteiro de blocos no heap ocupa 24 mordidas, então estamos usando seis vezes mais memória que o cenário. Sem boxe, a diferença é bastante dramática. Passei de quatro kilobytes de alocadores. Memória de calor a 24 megabytes e de introduzir a um milhão de formigas também, o que exigiu cinco coleções adicionais de geração zero para remover tudo do quadril. Portanto, a primeira regra para melhorar alocações de memória em seus códigos é evitar boxe em unboxing onde você pode, porque ele tem a tendência indesejada de inundar absolutamente o heap com lotes e lotes de pequenos objetos. Aqui está um resumo do que aprendemos nesta palestra de boxe. Unboxing é um processo que permite que você use tipos de valor em tipos de referência intercambiavelmente em seu casaco. Cada operação de boxe cria um novo objetos no heap. Uma caixa de objetos ocupa mais memória do que o tipo de valor original. boxe pode inundar o calor com muitos objetos pequenos. Coloque uma pressão considerável sobre o coletor de lixo. boxe no unboxing deve ser evitado sempre que possível. 13. Dica #5: não adicione muitas cadeias: A coisa boa sobre esta seção é que você pode ler a conclusão a partir do título da palestra. Então, aparentemente, você não deve inventar cordas inatas juntos. Mas por que não? O que há de tão ruim em juntar algumas cordas? Bem, vamos descobrir. Escrevi um pequeno programa de teste para investigar os efeitos que a concatenação de string terá nos braços de alocação de memória, lixo, comportamento coletor. Dê uma olhada nisso. Eu tenho a mensagem principal do programa aqui em tudo o que ele faz é chamar um off para testar métodos. Os primeiros métodos é chamado de string Testes, e é declarado aqui. Você pode ver que é tudo o que eu estou fazendo é construir uma cadeia de caracteres de 10 milhares consistindo inteiramente de caracteres hash. Começo com uma string vazia e, em seguida, em um loop, adiciono um único caractere 10.000 vezes. Agora, a primeira coisa que eu quero fazer é definir uma medição de linha de base. Então eu vou comentar a chamada para teste de string na mensagem principal aqui. Então, agora o programa não faz absolutamente nada, e podemos observar como o coletor de lixo se comporta nessa situação. Também podemos determinar quantos objetos estão presença no heap por padrão. Vou construir o projeto usando o menu build ativado, em seguida, mudar para a linha de comando para que eu possa executar o programa com o look profiler que está embutido no framework Mona que você viu na palestra anterior. Isso é tudo o que você precisa fazer é chamar os comandos Executar Herbal com os comandos gemidos Oh ativados e, em seguida, fornece esses argumentos de perfil para habilitar o profiler de log em Set it para exibir os relatórios de desempenho diretamente no console. Ok, aqui vamos nós. Estou executando o programa de base. Não, e aqui estão os resultados. O coletor de lixo executa duas coleções de geração um. Existem 19 cadeias de caracteres no heap, que ocupam ligeiramente sobre um kilobytes de memória. Memória total de heap é 3496 bytes alocados por 33 objetos. Então agora eu vou mudar de volta para desenvolver um modelo NCAA, significava a chamada para teste de string, reconstrói o programa, mudar de volta para a linha de comando e executar o programa novamente. Vamos ver o que é tipo fora de impacto a concatenação de string vai ter. Verifique isso mais. Olha para isto. Existem agora três gerações, zero coleções em sete coleções de geração um no resumo de alocação, podemos ver que existem 10 milhares e 20 cadeias fora do heap, ocupando cerca de 100 megabytes fora da memória. Há também uma mensagem seção de resumo legal aqui em baixo. Podemos ver que fizemos 10.000 chamadas para o método de string dot com cat, que é exatamente como esperado. Mas os métodos de gatos concha, chamados de outros métodos esfria alocações internas. Str Este método foi chamado 9999 vezes neste resultou em quase 15.000 operações de cópia de memória . O que está acontecendo aqui? A razão para esse comportamento é que os fluxos são objetos imutáveis em dot net. Simplificando, isso significa que os dados de string nunca podem ser modificados de forma alguma. Agora, isso pode soar um pouco estranho, porque se assim fosse, como métodos como substituir gatos Khan podem funcionar? Bem, a resposta é que esses métodos não modificam diretamente a string. Em vez disso, eles criam em seqüência inteiramente nova no heap com todas as modificações. Então, esta linha, vamos deixar a variável de texto completamente inalterada. Andi, em vez disso, cria uma cópia fora do texto antes das substituições nesta cópia em, em seguida, retornar a cópia e histórias nos resultados. Variável. Esta é uma regra de ouro. Off string operações em dot nets string mensagem. Nunca modifique a string original. Em vez disso, eles sempre fazem uma cópia. Modifique a cópia em vez disso ao retornar essa cópia. Como resultado. A razão para este comportamento é que ele faz cordas se comportar como tipos de valor, significa que eles podem ser atribuídos e comparados por valor, o que é muito conveniente para desenvolvedores. Você nunca precisa se preocupar se as variáveis de string podem estar se referindo à mesma string no heap. Além disso, ele permite que o tempo de execução dot net execute alguma otimização legal em códigos de manipulação de strings para melhorar o desempenho. Então, no meu programa de testes, eis o que acontece. Começo com uma corda vazia. Quando eu adicionar o primeiro caractere. Os métodos do cat calm não modificam a variável string. Em vez disso, ele faz uma cópia fora do fluxo, adiciona o caractere para a cópia e atribui a cópia de volta à variável string. No próximo loop, é aeração. A mesma coisa acontece de novo. A string de um caractere é copiada. Um segundo caractere é adicionado à cópia na cópia é atribuído de volta à variável string Após 10 milhares de concatenação. Terei 9999 d cadeias referenciadas no heap, mais uma string ativa com o resultado final. Isso é imensamente ineficiente, então acontece que as strings são realmente otimizadas para comparação rápida, e elas não são muito boas em modificações de livros para executar muitas modificações em uma única string. Há uma classe muito melhor desenhada especificamente para esse propósito. O construtor de cordas, um construtor de cordas, comporta mais como o que você esperaria uma string para ser um buffer de caracteres na memória que você pode livremente direito quando você tia, um personagem dedo do pé um construtor de cordas. Não há cópias acontecendo nos bastidores. Em vez disso, o framework dot net simplesmente grava esse caractere diretamente na string. Então vamos modificar o programa para usar um construtor de strings em vez disso e ver como isso afeta o padrão de alocação de memória. Aqui está o meu programa de novo. Há outros métodos de teste chamado teste String Builder. Você pode ver que é basicamente o mesmo casaco, mas ele usa um construtor de cordas em vez de upender os 10.000 caracteres. Vou mudar os principais métodos do programa para arrefecer os métodos de teste do construtor de cordas em vez disso. Ok, então agora novamente, a rotina familiar fora da construção do programa, alternando para a linha de comando na execução do programa com log profiling habilita. Aqui vamos nós. E aqui estão os resultados. O coletor de lixo está de volta a apenas duas coleções de geração 1, que é idêntico às nossas medições de linha de base. Assim, os casacos construtor de cordas não colocam pressão extra. Bem, o coletor de lixo. Tia Fantástica, Aqui está o resumo alocação. Agora tenho 31 cordas ocupando um pouco mais de 64 kilobytes. Esta é uma melhoria maciça. Eu passei de 100 megabytes para 64 kilobytes, que é uma melhoria de 99,9% nas pegadas de memória. Mas aguente firme. Minha string eventualmente contém 10 milhares de caracteres diretamente em um caractere Unicode em .net ocupa duas mordidas. Então, por que eu tenho 64 kilobytes alocados por 31 strings? Minha string não deveria ser apenas 20.000 bytes? E a resposta é que o construtor de cordas inicialmente só tem uma pequena quantidade de memória de caracteres disponível. Quando o buffer está cheio, o construtor de fluxo aloca um novo buffer fora o dobro do tamanho em copia todos os dados para este novo buffer. Assim, para 10.000 caracteres, nós progressivamente passamos por tamanhos de buffer de um a quatro oito em 16 kilobytes até que eventualmente alcancemos 32 kilobytes, que é suficiente para armazenar 10.000 caracteres Unicode. O alguns off 1248 16 e 32 é 63 kilobytes. Exatamente o que estamos vendo na pilha agora para garantir que eu alocar apenas a quantidade exata de memória necessária que eu preciso para inicializar o construtor de cordas para o tamanho correto. Se o buffer de caracteres começar com 10.000 caracteres imediatamente, não há necessidade de dobrar o tamanho enquanto o loop estiver sendo executado. Isso acelerará meu programa e reduzirá as pegadas de memória para implementar a mudança . Tudo o que eu preciso fazer é Tia um argumento construtor aqui especificando a capacidade inicial do construtor de cordas em caracteres. Vou inicializá-lo para exatamente 10 milhares de caracteres, então tudo se encaixa bem, mas nós construímos uma mudança de programa sobre a linha de comando para executar o programa com perfil Lok . Aqui vamos nós. Aqui estão os resultados 22 cordas ocupando um pouco mais de 20 kilobytes. Assim como seria de esperar, eu removi mais 70% de desconto alocações de memória desnecessárias. Aqui está um resumo sobre o que temos aprende nesta mensagem de texto de palestra. Nunca modifique a string original. Em vez disso, eles fazem uma cópia. Modificar a cópia tia retorno Isso é cordas de café são otimizadas para comparações rápidas. A classe construtor de cordas é otimizada para modificações rápidas. Sempre use construtores de strings ao modificar strings em Mission Critical Code No meu programa de teste , alternar de strings para construtores de strings reduziu as pegadas de memória em 99,9%. 14. Dica 6: use as estruturas em em vez de aulas: Nesta palestra, vou dar uma olhada mais de perto em Stingidos O que exatamente são golpeados e como eles diferem das aulas? Muitas pessoas ficam confusas quando têm que explicar a diferença entre Stingidos e classes. Vamos passar por eles um por um. Nós cobrimos a diferença entre tipos de valor sobre os tipos de referência na seção fundamental para re valor copo. Os tipos são armazenados em linha na pilha ou no heap, enquanto os tipos de referência sempre se referem a um objeto armazenado em outro lugar na Web. Voltaremos a essa diferença mais tarde nesta palestra, onde você verá que ela tem um efeito dramático sobre a pegada de memória no comportamento do coletor de lixo . Como os traçados são tipos de valor, eles são atribuídos e comparados por valor. Quando você atribui Atingido, o tempo de execução dot net cria ons de um ataque totalmente novo. Cópias tudo se sente acabado. Compare isso com uma classe onde a referência suas cópias sobre Andi você acaba com duas referências para o mesmo objeto no Heeb. Isso nunca pode acontecer com Stingidos, porque os traços são tipos de valor que eles não podem herdar de uma classe base, você está livre para implementar interfaces, mas você não pode herdar qualquer tipo de implementação. Outra restrição um pouco estranha é que os campos internos não podem ter inicializar Er. Todos os campos são inicializados para seu valor padrão automaticamente, e você só pode substituir seus valores no construtor. E falando em construtores, um golpe não pode ter um parâmetro menos construtor. Você deve declarar pelo menos um argumento. A razão para essa restrição aparentemente estranha é que o tempo de execução inicializado instrui zerando sua memória. Em contraste, as classes são inicializadas chamando seu construtor padrão. Isso faz com que Struck inicializado muito mais rápido, e, em seguida, classes e finalmente golpeados não podem ter finalizar er. Isso é bastante óbvio quando você considera que acertado é o nosso valor. Tipos no coletor de lixo, que é responsável por executar Finalize Er sempre ignorará os tipos de valor. Ele pode fazer isso com segurança porque os tipos de valor não precisam ser explicitamente limpos. Pense sobre isso. Quando um tipo de valor reside na pilha, ele será limpo quando o frio retorna do método e o quadro de pilha correspondente é descartado, e quando um tipo de valor reside no heap, ele será limpo quando ele está contendo tipo é coletado pelo coletor de lixo. Esta é uma coisa importante para lembrar o coletor de lixo Onley marcas e varre tipos de referência. Ele sempre irá ignorar os tipos de valor, independentemente, como eles são usados. Ok, então essa foi uma breve introdução sobre a diferença entre Struck e classes. Agora vamos ver como eles se comportam em um programa de teste real. Dê uma olhada no seguinte código. Eu tenho a Razão. Um programa de teste curto que aloca um milhão de objetos de ponto em armazena-los em uma lista genérica . Meus objetos de ponto são contêineres simples para um X em um valor Y. Muitas vezes, você vê esses tipos de classes em programas que precisam rastrear pixels, coordenadas de mapa ou vetores matemáticos. Então minha mensagem principal do programa aqui esfria um único método de teste chamado classe de teste. Os métodos de classe de teste aqui localiza um milhão de classes de pontos em um loop. Inicializar são eles usando o contador de loop. Andi adiciona-os a esta lista genérica. Esta é a declaração de classe para a classe de ponto. Você pode ver que é muito simples apenas para campos públicos para as coordenadas X e Y. Onda, um construtor para inicializar os pontos. Antes de executar este programa, preciso de medidas de linha de base para ver que tipo de objetos estão presentes no heap por padrão. Antes de executar este programa, preciso de medidas de linha de base para ver que tipo de objetos estão presentes no heap por Então eu vou comentar a chamada para os métodos de classe de teste na mensagem principal do programa . Agora o programa não faz absolutamente nada, e podemos usar isso para criar uma linha de base para nosso relatório de perfil. Estou criando uma compilação de lançamento agora, depois mude para a linha de comando para que eu possa executar o programa com o profiler de log. Aqui vamos nós. E aqui estão os resultados que temos para a geração 1 coleções pelo coletor de lixo. Há 21 movimentos de objeto devido a ciclos de compactação de heap no contém 3552 mordidas alocadas por 33 objetos. Certo, de volta aos códigos. Vou remover os comentários para que possamos executar uma medida para a implementação que usa classes. Vamos ver como a pilha se parece depois de alocar um milhão de classes de pontos. Ok, cria uma lista de liberação mudar para a linha de comando em Executar o programa. Aqui vamos nós. E aqui está a nossa resposta. Este código coloca uma carga sobre o coletor de lixo. Temos quatro coleções de geração zero e três coleções de geração 1 mais do que três vezes mais do que a medição de linha de base. O coletor de lixo executa 87.224 movimentos de objeto ao compactar o heap. Isso é muito fora de operações de cópia de memória, que afetará negativamente o desempenho deste programa em Olhe para o resumo de alocação aqui meu array está aqui, uma única instância ocupando megabytes de AIDS fora do espaço térmico. Isso é o que eu esperaria porque em objetos de referência em um sistema de 64 bits é oito bytes tamanho, um milhão de objetos vezes oito bytes é oito megabytes, e aqui estão as instâncias de pontos. Tenho um milhão de pontos ocupando 24 megabytes fora do espaço. Então, no total, meu programa alocou 32 megabytes fora da memória dele. Finalmente, nos métodos chamados resumo enfraquecer ver o tempo total de execução fora do programa. São dois pontos 37 segundos. Agora você pode estar se perguntando como um milhão de pontos somam 24 megabytes. Minha classe de ponto contém apenas dois inteiros fora de quatro bytes cada, então você esperaria que um milhão de pontos ocupasse apenas megabytes de AIDS. O que aconteceu aqui? A resposta é que cada objeto introduz 16 bytes fora da sobrecarga, que é usado para dados de limpeza, modo que cada ponto é na verdade 24 bytes de tamanho, três vezes maior do que o esperado. Vamos ver se eu posso tornar este programa mais eficiente usando Struck em vez de classes, eu vou mudar a mensagem principal do programa para chamar os métodos de teste atingido. Em vez disso, você pode ver a partir da declaração que o método é praticamente o mesmo que teste Classe oito aloca um milhão de pontos e armazena-os em uma lista. A única diferença é que agora estou usando pontos atingidos em vez da classe de pontos. Então, o que é um ponto atingido? Confira a declaração aqui. É simplesmente instruir com campos públicos X e Y em um construtor para inicializar os campos. Assista quando eu alternar rapidamente entre a classe na estrutura. Veja, eles são praticamente idênticos. Certo, de volta à mensagem principal do programa. Vou mudar a mensagem de teste. Chamada para usar Stinks em vez disso, e agora o ritual habitual cria uma conta. Alterne para os ponteiros da linha de comando executam o programa. Ele teve outros resultados. Estamos de volta a duas coleções de uma geração e 21 movimentos de objetos, que é idêntico à linha de base. Portanto, agora não há pressão sobre o lixo coleta ou qualquer coisa. O resumo da alocação mostra a matriz ocupando oito megabytes de dados e nada mais. Então, agora a memória total de heap é apenas oito megabytes, um surpreendente quatro vezes. Melhorias nas pegadas de memória. Como é que isso aconteceu? Para entender a diferença, temos que visualizar como os dados são armazenados na memória. Vamos começar com o código de classe de pontos. Uma lista de um milhão de pontos classes parece assim na pilha. Cada item na lista é uma referência de oito bytes para uma instância de classe de ponto em outro lugar na Web, e cada instância de classe de ponto consiste em 16 bytes de dados de limpeza. Tia oito morde dados reais de campo, então 24 mordidas no total. Isso tudo adiciona até oito megabytes para a lista em 24 megabytes para as instâncias de pontos, ou 32 megabytes no total. Agora compare isso com o atingido. A implementação, como você sabe, instrui nossas lojas em linha, então isso significa que elas são armazenadas diretamente nos itens da lista. Não há referências neste cenário. Cada golpe tem zero sobrecarga e ocupa apenas oito mordidas de sua memória para os dois inteiros. Assim, toda a matriz ainda tem oito milhões de bytes, mas sem quaisquer objetos adicionais no assim. Isso reduz as pegadas de memória para oito megabytes, uma melhoria de quatro vezes. Porque agora existe apenas uma única matriz no heap. O coletor de lixo não precisa fazer nenhum trabalho extra. É simplesmente limpa a matriz porque os pontos são armazenados dentro da matriz. Isso também limpa todos os 1 milhão de pontos gratuitamente. Finalmente, vejamos o tempo de execução do programa. tempo total de execução agora é de apenas 322 milissegundos. A razão para isso é que esta implementação não tem que chamar um milhão de pontos Construtores armazenar. Um milhão de referências de heap na matriz. Onda move 870.000 objetos. Dois. Compact, A pilha que economiza muito tempo. Certo, então Struck é ótimo, e devemos sempre usá-los, certo? Bem, sem estrutura, ótimo, mas você só deve usá-las em cenários muito específicos. Aqui está uma lista. Use traçados Quando os dados Sua história representa um único valor. Exemplos são um ponto. Um vetor na matriz, um número complexo, uma parte de valor. Um novo aluno médico, etc., use struts. Se o tamanho dos dados for muito pequeno, 24 mordidas ou menos. Andi, você ia criar milhares ou milhões de instâncias. Use o Struck. Se seus dados são imutáveis e devem ser atribuídos e comparados por valor, use Stingidos. Se seus dados não precisarem ser encaixotados frequentemente em todos os outros cenários, as classes são uma alternativa melhor. Resumindo, Struck é nosso tipo de valor. Classes são tipos referenciados. Stingidos são muito mais rápidos do que as classes porque eles não têm um construtor padrão. Eles não podem ser herdados tia. Eles não são coletados pelo coletor de lixo. Stingido apenas aloca memória de pilha para seus campos internos, e eles não têm a sobrecarga de 16 mordidas que objetos no calor têm em determinados cenários. Usando golpeados em vez de aulas pode reduzir drasticamente a pegada de memória no tempo de execução fora de seu casaco. Meu programa de teste com Struck localizei quatro vezes menos memória e correu sete vezes mais rápido, em seguida, a implementação que usou classes 15. Dica #7: sempre alocar coleções: nesta palestra, eu gostaria de focar em um aspecto interessante das coleções que muitas vezes é negligenciado. Mas primeiro, vamos dar uma olhada na palestra anterior de concatenação de string, você vai se lembrar que nós olhamos para onda de concatenação de cordas. Descobrimos que as cordas são imutáveis. Um pendente lotes de caracteres para uma string resultou em uma terrível pegada de memória. Como a string inteira foi copiada no heap durante cada concatenação, corrigimos o problema usando um construtor de string em vez disso. Um construtor de cordas é um buffer de caracteres na memória, com uma certa capacidade que você pode diretamente direitos em substituir cordas com construtores de cordas fez uma enorme melhoria para a pegada de memória fora do meu casaco. Isso é você se lembra o que acontece quando eu rodei os códigos de construtor de cordas pela primeira vez . Minha cadeia de caracteres de 10 milhares acabou ocupando 64 kilobytes de espaço de pilha. A razão para isso é que o construtor de cordas inicialmente só tem uma pequena quantidade fora do caractere. Memória disponível Andi. Quando o buffer fica sem espaço, é simplesmente aloca. Um novo buffer desligado Duas vezes o tamanho em copia tudo para o novo buffer. O redimensionamento consecutivo está fora de 1 a 4, 8 16 e 32 kilobytes resultou em 63 kilobytes fora do espaço de calor alocado, muito mais do que eu realmente precisava para o fluxo. Agora aqui está uma regra importante. Todas as coleções da lista de redes internas fazem a mesma coisa. Uma lista começa com uma certa capacidade padrão quando fica sem espaço, ele irá criar uma nova lista fora duas vezes o tamanho mãos copiar tudo sobre. Isso aumentará as pegadas de memória dos nossos códigos e afetará negativamente o desempenho. Porque fora de todas as operações de cópia de memória acontecendo nos bastidores, vamos fazer um monte de medidas para descobrir quanto redimensionamento de listas de despesas gerais de memória irá introduzir. Dê uma olhada neste código. Declaro um monte de coleções aqui. Honore lista um que é preso, uma lista genética no dicionário. Meu programa principal, Messes, chama um único método chamado em suas coleções, que adiciona um único item a cada coleção. Isso é necessário porque algumas coleções implementa carregamento lento. Eles inicializam seu armazenamento interno somente quando você adiciona o primeiro item. Então, para forçar cada coleção a inicializar, eu preciso adicionar pelo menos um item a cada um deles. Agora, o próximo passo é um pouco estranho para descobrir qual é a capacidade padrão. Eu preciso olhar dentro das aulas de coleta em sua implementação privada interna. Não há como eu conseguir isso no frio, exceto usando reflexão. Mas uma maneira muito mais fácil é executar meu programa no modo de depuração, definir um ponto de interrupção e, em seguida, olhar dentro das coleções com o desbunker. Então eu vou colocar um ponto de ruptura aqui e do programa. Agora posso usar a janela do relógio para inspecionar cada coleção. Tudo começa com a lista de arrays. A matriz interna será um membro privado, então eu preciso expandir a pasta de membros não públicos aqui. Ele está em Object Array chamado itens com um comprimento fora. Quatro. A seguir é a deixa. O storage array interno é novamente um membro privado chamado Array, com o comprimento de quatro. O próximo é o Stuck. O storage array interno está no array entrevistado chamado Array, com um comprimento inferior a 16. Em seguida, a lista genérica. O storage array interno é, um, array entrevistado chamado itens com o comprimento ou quatro, e finalmente, o dicionário. O dicionário tem muitas matrizes internas, mas vou me concentrar neste aqui chamado chave Slots, que tem um comprimento fora 12. Então, para resumir uma lista de raios quatro itens que quatro itens presos. 16 itens listam quatro itens. Dicionário, 12 itens. Você pode ver que estas são pequenas capacidades iniciais. Se você começar a adicionar centenas ou milhares de itens em uma coleção, ele terá que redimensionar muitas vezes para acomodar todos os itens. Vamos ver o quão grande as pegadas de memória podem ficar. Vou modificar meu programa para preencher as listas genéticas com algumas centenas de milhares de itens. Então aqui na mensagem principal do programa, vou comentar a chamada em suas coleções. A Andi. Em vez disso, coloque em uma chamada para a lista de preenchimento checkouts método. Os métodos da lista de filmes. Aqui eu uso um loop para adicionar exatamente 262.145 inteiros à lista. Então, quanta memória é necessária para a lista? Cada item é um número inteiro de quatro mordidas, que é um tipo de valor que é armazenado em linha em cada item de matriz, Então, a pegada total de memória será quatro vezes 262 milhares. 145 é 1.048.580 mordidas ou praticamente um megabytes fora do armazenamento. Certo, vamos testar essa teoria. Vou criar uma compilação de lançamento tia, executar o profiler de bloqueio para descobrir exatamente qual será a pegada de memória. Aqui vamos nós. Estou executando o programa agora. Mãos aqui nos resultados. A matriz inteira ocupa 4.195.168 mordidas, ou ligeiramente mais de quatro megabytes. O que aconteceu aqui é que a lista genética continuou redimensionando sua matriz interna uma e outra vez até que eventualmente, todos os 262 milhares itens se encaixam na matriz, mas eu não escolhi esse número aleatoriamente. 262,145 vezes 14 byte inteiro é exatamente um megabytes mais quatro mordidas, então o buffer precisa expandir para dois megabytes para sentir ou itens on porque ele já está circulado através de todos os poderes de procedimento de dois, o heap total alocado memória é de quatro megabytes. Isso representa o pior cenário absoluto. Todos os itens, exceto o final, se encaixam na matriz, então a lista tem que dobrar de tamanho para acomodar a última noite. Você fica com uma pegada de memória que é quatro vezes maior do que o que realmente é necessário. Você fica com uma pegada de memória que é quatro vezes maior do que o que realmente é A solução para este problema é super fácil. Tudo o que você precisa fazer é inicializar a lista para o tamanho correto. Deixe-me fazer isso agora no frio. Então tudo o que tenho que fazer é mudar a lista. Declaração aqui, Andi, adicione o valor do item marcas para o construtor fora da lista. Agora eu preciso criar uma nova compilação de versão e, em seguida, mudar para a linha de comando para executar o programa novamente. E aqui estão os resultados. As pegadas de memória estão de volta a um megabytes, uma melhoria de quatro vezes. Deixe-me resumir o que vimos nesta palestra. Todas as redes internas das coleções são inicializadas para uma pequena capacidade padrão e duplicam automaticamente seu tamanho quando estão cheias. Esse comportamento pode resultar em um pior caso pegadas de memória que é quatro vezes maior do que necessário. Para evitar esse problema, você deve inicializar a coleção para o tamanho correto 16. Dica #8: não materialize as expressões LINQ: nesta palestra, Eu quero me concentrar em inesperado lamentável ao escrever consultas de link que podem inflar muito as pegadas de memória de seus códigos. Link foi introduzido em C Sharp versão quatro. A Andi. É uma linguagem muito agradável, claramente, um pouco semelhante ao SQL para realizar consultas complexas em inúmeras fontes de dados. Link tem muitas funções incorporadas para filtragem, projetando agregação na junção de dados. A Siri está aqui como funciona. Nos bastidores link pode operar em qualquer dado que implementa a interface I inumerável . Inumeráveis dados é basicamente uma coleção de itens que você pode percorrer um por um. O I inumeráveis infecta contém apenas um único método chamado Gets in Numerator, por exemplo, odiando em numerador para percorrer os dados. Os objetos do operador em também tem sua própria interface. Frio I em numerador. Contém apenas três membros. Mover ao lado para mover para o próximo item da série atual para recuperar o item atual, Tia Reset para saltar de volta para o início fora da Siri o fim Numerador implementa um tipo fora para frente apenas amaldiçoar todos para recuperar os itens na Siri um por um . Quando você usa o para cada instrução, O compilador cria em M aerador nos bastidores para percorrer todos os itens na Siri um por um. A coisa boa sobre Link é que ele pode empilhar operações no topo, muitas vezes no operador, sem realmente executá-los. Considere o seguinte código que esta expressão prepara. Organize 500 números de probabilidades, mas na verdade não gera os números ainda. Tudo o que ele faz é criar no numerador Andi. Adicione uma expressão de filtro a ela quando você percorrer o intervalo. Por exemplo, usando o para cada instrução. O aerador N M usa o movimento. Próxima mensagem. Tia aplica a expressão de filtro para retornar os valores corretos. Outra cena interessante para lembrar é que o intervalo não ocupa nenhuma memória. O operador de entrada, Onley, rastreia o número atual. A Andi. Ele tem uma expressão de filtro para calcular o próximo número no intervalo, modo que o espaço total de memória fora do operador in é apenas o tamanho de um único Eisen. Mesmo que o intervalo descreva 500 itens, você pode usar link para processar quantidades muito grandes de dados enquanto usa apenas uma pequena quantidade fora memória hip porque o numerador só rastreará o item atual. Mas link pode se comportar de maneiras inesperadas. Considere o seguinte programa. Escrevi um corretor ortográfico em C. Sharp. Você pode ver que este projeto é chamado de corretor ortográfico ruim porque ele é alocado liberalmente . memória no heap em produz uma enorme pegadas de memória. Vamos dar uma olhada. Vou começar com este método de arquivo de leitura. Aqui ele abre um arquivo de texto lê linha por linha em usa as instruções yield return para retornar cada linha como um novo item em uma seqüência inumerável off strings. Com efeito, todos os métodos se transformam em uma cratera enorme. As instruções yield return retornam. O valor atual no método move next irá avançar o loop while para a próxima linha No arquivo abaixo aqui é a mensagem principal do programa. Eu li o arquivo todas as palavras, que contém um dicionário de aproximadamente 150 milhares, palavras corretamente escritas. Depois carrego outra história de chamadas de arquivo, que é a primeira linha da polícia da vítima. Um artigo sobre o país fora da Espanha. Então eu chamo o feitiço Check Methods, que é declarado aqui em cima. Verificação ortográfica usa uma consulta de link para percorrer cada palavra na história. Para cada palavra eu gero a versão minúscula fora das palavras e, em seguida, projeto um novo para apple, consistindo nas palavras originais e um valor booleano, indicando se as palavras poderiam ser encontradas no dicionário novamente. Esta linha de código não gera a sequência. É simplesmente construir um numerador muito complicado para ou executar um feitiço. Verifique cada palavra na história. A sequência está finalmente sendo gerada aqui no para cada instrução. Passo por cada aluno nos resultados. Defina o dedo do pé da cor do console verde ou vermelho, dependendo se a palavra foi encontrada no dicionário ou não e exiba as palavras no console, além de um espaço à direita. Então este programa deve exibir toda a história. Palavras por palavras Fãs destaca quaisquer erros ortográficos em vermelho. Vamos executar o programa para verificar se tudo funciona, assim como em palestras anteriores, Eu vou gerar uma versão build Andi. Então eu vou mudar para a tia console. Eu executo o programa na linha de comando com o perfil Log, que possamos olhar para todos os objetos sendo alocados no heap. Aqui vamos nós. Você pode ver que o desempenho desse código não é ótimo. Isto é como uma palavra par de segundos ou assim. A este ritmo, levaria uma eternidade para verificar todo o artigo da Wikipédia. Então eu acho que tenho sorte de eu estar apenas checando a primeira frase. Ok, então vamos esperar até que o programa termine. Aqui estão os resultados e eles não são bons. O coletor de lixo executa 2,6 milhões de movimentos de objetos enquanto compacta o havia 24 coleções de geração zero em 12 coleções de geração um. No resumo de alocações, podemos ver que eu tenho 2,7 milhões de strings no Heeb ocupando 128 megabytes. Eu também tenho 349 matrizes de cadeia ocupando outro 78 megabytes total. Memória de heap alocada é mais de 200 megabytes no método chamado resumo mostra que o tempo de execução total do programa é 38 pontos cinco segundos. O que acontece aqui? Deixe-me mostrar que não estava errado aqui. Eu vou mudar de volta para Mona desenvolvido Agora olhar para este método listar aqui. Eu preciso da mensagem porque eu tenho que verificar se as palavras na história aparecem no dicionário. Eu faço isso com o método contém. Mas para chamar esses métodos eu primeiro preciso converter as palavras do dicionário em admiração em uma lista que Aiken busca. Agora aqui está o problema. A variável de dicionário em si está no aerador M que carrega a lista de palavras sob demanda. Então, para cada palavra na história, este código vai recarregar toda a lista de palavras convertida em uma lista apenas para verificar essa única palavra. Eu tenho 18 palavras na minha história, então eu acabo tendo o dicionário inteiro na memória 18 vezes. Há 145.000 palavras no dicionário, o que me deixa com 2,7 milhões de cordas. Ocupando 128 megabytes. A lista de dicionários usa na matriz de agitação interna para armazenamento. O dicionário requer um pouco mais de um megabytes fora do armazenamento, de modo que a lista terá aumentado para dois megabytes de capacidade em todas as matrizes de cadeia descartadas anteriores adicionam mais dois megabytes, modo que cada dicionário ocupa quatro Megabytes fora de sua memória ligado. Desde que eu tenho 18 off eles fizz como até 72 megabytes fora de memória de calor. Juntos, isto soma-se a oito, impressionante a centenas de megabytes de memória térmica, um enorme desperdício de memória nisto é o lamentável que eu estava a falar. Não é imediatamente óbvio a partir do dicionário valioso que é realmente no operador que carrega todo o dicionário no monte. O verificador ortográfico frio parece razoável, mas somente se o dicionário é realmente descontado inteiramente na memória, para que possamos consultá-lo rapidamente. E este não é o caso aqui. Quando você está escrevendo consultas de link complexas, é muito fácil de perder. Acompanhe a implementação fora dos enumeradores que você usa. Quão complexa é a implementação fora do operador in? Quantos itens ele expõe? Você precisará dessas informações antes de decidir como fazer. Claramente a seqüência efetivamente link implementa uma camada muito abstrata claramente no topo off C revestimento afiado, e às vezes a implementação fora os inúmeros dados subjacentes pode voltar a morder você de maneiras inesperadas. A boa notícia é que não é difícil consertar o corretor ortográfico. Fiz isso em outro projeto chamado Verificador Ortográfico Bom. Vamos dar uma olhada. O corretor ortográfico bom é muito semelhante ao ruim, mas uma diferença notável é que eu declaro o dicionário para ser uma lista genérica fora de strings aqui em cima esta mudança remove a necessidade de os métodos para listar. Chame o casaco do corretor ortográfico. Note também que inicializo a lista. 250.000 itens. Isso impede que a lista dobre o tamanho ao adicionar itens. Andi remove todos os d referenciados matrizes de cadeia no heap. A mensagem de verificação ortográfica é quase igual ao casaco estragado. A única diferença é que agora eu posso chamar os métodos contém diretamente na variável dicionário porque já é uma lista genética. A mudança final está na mensagem principal do programa aqui em baixo. Agora inicializo o dicionário usando isso para cada instrução. Ok, vamos ver como esta versão combina. Estou criando uma compilação de versão mudando para a linha de comando ao executar o programa usando o profiler de log. Aqui vamos nós. E aqui estão os resultados. Estamos reduzidos a 145.000 movimentos de objetos. Uma geração zero coleção na geração um coleções muito melhor. A pilha agora contém 145 000 cadeias de caracteres. Este é o nosso dicionário ocupando 6,8 megabytes fora da memória de heap. Eu também tenho nove matrizes de cadeia ocupando 1,2 megabytes. Isso é exatamente o que você esperaria. Uma matriz de cadeia de caracteres com 145.000 elementos cada em referência de memória de pilha de oito bytes ocupará um pouco mais de um megabytes fora da memória exatamente o que estamos vendo aqui. Portanto, esta implementação só irá alocar o dicionário de palavras no heap em nada mais. Isso reduz as pegadas de memória para oito megabytes, uma melhoria maciça de 25 vezes. Então, o que aprendemos? Link é uma estrutura poderosa para executar consultas em inúmeros dados chamando os métodos de lista para em um link, expressão pode inesperadamente inflar as pegadas de memória fora de seu casaco para corrigir esse problema. Legal listar antes de executar a consulta de link Em meus códigos de verificação ortográfica pré-inicializar o dicionário de palavras resultou em uma pegada de memória 25 vezes menor. 17. Receita do curso: Parabéns. Você completou todo o curso. Agora você é um otimizador de memória C afiada certificado. Eu lhe mostrei Como funciona a coleta de lixo Nets? Como otimizar seus próprios códigos para coleta de lixo? Andi eu demonstrei vários truques simples para melhorar drasticamente as pegadas de memória do seu casaco. Analisamos detalhadamente a coleta de lixo, identificamos as suposições que o coletor de lixo faz sobre o tamanho de objetos em Lifetime e pesquisamos como você pode otimizar seu próprio código para trabalhar com essas suposições, também cobrimos a memória otimização. Medimos os efeitos de vários truques simples, onde uma pequena mudança de código resultou em grandes melhorias nas pegadas de memória ao largo da costa. As habilidades que você aprende lhe deram uma rica caixa de ferramentas de conhecimento e idéias que você pode usar ao escrever seus próprios códigos ou ao colaborar em uma equipe de desenvolvimento, especialmente quando você está trabalhando em missão crítica fria, onde baixo uso de memória e desempenho rápido é crucial. Se você descobrir alguns insights interessantes por conta própria, por favor, compartilhá-los na discussão do curso para ele, para todos nós para desfrutar. Adeus. Espero que nos encontremos novamente em outro curso