Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Importante
As técnicas descritas nesta seção melhoram o desempenho quando aplicadas a hot paths em seu código. Os caminhos quentes são as seções da sua base de código que são executadas com frequência e repetidamente em operações normais. A aplicação dessas técnicas a códigos que não são frequentemente executados terá um impacto mínimo. Antes de fazer qualquer alteração para melhorar o desempenho, é fundamental medir uma linha de base. Em seguida, analise essa linha de base para determinar onde ocorrem gargalos de memória. Você pode aprender sobre muitas ferramentas multiplataforma para medir o desempenho do seu aplicativo na seção Diagnóstico e instrumentação. Você pode praticar uma sessão de criação de perfil no tutorial para medir o uso de memória na documentação do Visual Studio.
Depois de medir o uso de memória e determinar que é possível reduzir as alocações, use as técnicas desta seção para reduzir as alocações. Após cada alteração sucessiva, meça novamente o uso da memória. Certifique-se de que cada alteração tem um impacto positivo no uso de memória em seu aplicativo.
O trabalho de desempenho no .NET geralmente significa remover alocações do seu código. Cada bloco de memória alocado deve ser eventualmente liberado. Menos alocações reduzem o tempo gasto na coleta de lixo. Ele permite um tempo de execução mais previsível, removendo coleções de lixo de caminhos de código específicos.
Uma tática comum para reduzir as alocações é alterar estruturas de dados críticas de class
tipos para struct
tipos. Essa mudança afeta a semântica do uso desses tipos. Parâmetros e retornos agora são passados por valor em vez de por referência. O custo de copiar um valor é insignificante se os tipos forem pequenos, três palavras ou menos (considerando que uma palavra é de tamanho natural de um inteiro). É mensurável e pode ter um impacto real no desempenho de tipos maiores. Para combater o efeito da cópia, os desenvolvedores podem passar esses tipos para ref
recuperar a semântica pretendida.
Os recursos do C# ref
oferecem a capacidade de expressar a semântica desejada para struct
tipos sem afetar negativamente sua usabilidade geral. Antes desses aprimoramentos, os desenvolvedores precisavam recorrer a unsafe
estruturas com ponteiros e memória não processada para obter o mesmo efeito no desempenho. O compilador gera código seguro verificável para os novos ref
recursos relacionados.
Código verificável e seguro significa que o compilador deteta possíveis saturações de buffer ou acessa memória não alocada ou liberada. O compilador deteta e previne alguns erros.
Passagem e retorno por referência
As variáveis em C# armazenam valores. Em struct
tipos, o valor é o conteúdo de uma instância do tipo. Em class
tipos, o valor é uma referência a um bloco de memória que armazena uma instância do tipo. Adicionar o ref
modificador significa que a variável armazena a referência ao valor. Em struct
tipos, a referência aponta para o armazenamento que contém o valor. Em tipos class
, a referência aponta para o armazenamento que contém o bloco de memória.
Em C#, os parâmetros para métodos são passados por valor, e os valores de retorno são retornados por valor. O valor do argumento é passado para o método. O valor do argumento de retorno é o valor de retorno.
O ref
, in
, ref readonly
, ou out
modificador indica que o argumento é passado por referência. Uma referência ao local de armazenamento é passada para o método. Adicionar ref
à assinatura do método significa que o valor de retorno é retornado por referência. Uma referência ao local de armazenamento é o valor de retorno.
Você também pode usar a atribuição ref para que uma variável se refira a outra variável. Uma atribuição típica copia o valor do lado direito para a variável no lado esquerdo da atribuição. Uma atribuição ref copia a localização da memória da variável no lado direito para a variável no lado esquerdo. O ref
agora refere-se à variável original:
int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment
Console.WriteLine(location); // output: 42
sameLocation = 19; // assignment
Console.WriteLine(anInteger); // output: 19
Ao atribuir uma variável, você altera seu valor. Quando atribuí ref uma variável, alteras a que se refere.
Você pode trabalhar diretamente com o armazenamento para valores usando ref
variáveis, passar por referência e atribuição ref. As regras de escopo impostas pelo compilador garantem a segurança ao trabalhar diretamente com o armazenamento.
Os ref readonly
modificadores e in
indicam que o argumento deve ser passado por referência e não pode ser reatribuído no método. A diferença é que ref readonly
indica que o método usa o parâmetro como uma variável. O método pode capturar o parâmetro ou pode retornar o parâmetro por referência de somente leitura. Nesses casos, você deve usar o ref readonly
modificador. Caso contrário, o in
modificador oferece mais flexibilidade. Não é necessário adicionar o in
modificador a um argumento para um in
parâmetro, para que você possa atualizar as assinaturas de API existentes com segurança usando o in
modificador. O compilador emite um aviso se não adicionar o modificador ref
ou in
a um argumento para um parâmetro ref readonly
.
Contexto seguro de referência
O C# inclui regras para ref
expressões para garantir que uma ref
expressão não possa ser acessada onde o armazenamento ao qual ela se refere não é mais válido. Considere o seguinte exemplo:
public ref int CantEscape()
{
int index = 42;
return ref index; // Error: index's ref safe context is the body of CantEscape
}
O compilador relata um erro porque você não pode retornar uma referência a uma variável local de um método. O chamador não pode acessar o armazenamento ao qual está sendo referido. O contexto ref safe define o escopo no qual uma ref
expressão é segura para acessar ou modificar. A tabela a seguir lista os contextos ref safe para tipos de variáveis.
ref
Os campos não podem ser declarados em um class
ou um struct
não-ref, portanto, essas filas não estão na tabela:
Declaração | ref contexto seguro |
---|---|
local não referenciado | bloco onde a variável local é declarada |
parâmetro non-ref | Método atual |
ref , ref readonly , in parâmetro |
Método de chamada |
parâmetro out |
Método atual |
class campo |
Método de chamada |
campo não-referência struct |
Método atual |
ref campo de ref struct |
Método de chamada |
Uma variável pode ser ref
retornada se o seu contexto seguro por referência for o método de chamada. Se seu contexto ref safe for o método atual ou um bloco, ref
o retorno não será permitido. O trecho a seguir mostra dois exemplos. Um campo de membro pode ser acessado a partir do escopo que chama um método, portanto, o método de chamada é o contexto seguro de referência para um campo de classe ou struct. O contexto seguro de ref para um parâmetro com os modificadores ref
ou in
é o método inteiro. Ambos podem ser ref
retornados de um método membro:
private int anIndex;
public ref int RetrieveIndexRef()
{
return ref anIndex;
}
public ref int RefMin(ref int left, ref int right)
{
if (left < right)
return ref left;
else
return ref right;
}
Observação
Quando o ref readonly
modificador ou in
é aplicado a um parâmetro, esse parâmetro pode ser retornado por ref readonly
, não ref
.
O compilador garante que uma referência não possa escapar de seu contexto ref safe. Você pode usar ref
parâmetros, ref return
e ref
variáveis locais com segurança porque o compilador deteta se você escreveu acidentalmente código onde uma ref
expressão pode ser acessada quando seu armazenamento não é válido.
Contexto seguro e estruturas de referência
ref struct
Os tipos exigem mais regras para garantir que podem ser utilizados com segurança. Um ref struct
tipo pode incluir ref
campos. Isso exige a introdução de um contexto seguro. Para a maioria dos tipos, o contexto seguro é o método de chamada. Em outras palavras, um valor que não é um ref struct
sempre pode ser retornado de um método.
Informalmente, o contexto seguro para um ref struct
é o escopo onde todos os seus ref
campos podem ser acessados. Em outras palavras, é a interseção do contexto seguro de referência de todos os seus ref
campos. O método a seguir retorna um ReadOnlySpan<char>
para um campo membro, portanto, seu contexto seguro é o método:
private string longMessage = "This is a long message";
public ReadOnlySpan<char> Safe()
{
var span = longMessage.AsSpan();
return span;
}
Em contraste, o código a seguir emite um erro porque o membro do ref field
refere-se à matriz de inteiros alocada na pilha do Span<int>
. Ele não pode escapar do método:
public Span<int> M()
{
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
return numbers; // Error! numbers can't escape this method.
}
Unificar tipos de memória
A introdução de System.Span<T> e System.Memory<T> fornecem um modelo unificado para trabalhar com memória.
System.ReadOnlySpan<T> e System.ReadOnlyMemory<T> fornecem versões apenas leitura para aceder à memória. Todos eles fornecem uma abstração sobre um bloco de memória armazenando uma matriz de elementos semelhantes. A diferença é que Span<T>
e ReadOnlySpan<T>
são ref struct
tipos enquanto Memory<T>
e ReadOnlyMemory<T>
são struct
tipos. Spans contêm um ref field
arquivo . Portanto, as instâncias de um span não podem sair de seu contexto seguro. O contexto seguro de um ref struct
é o contexto seguro de referência do seu ref field
. A implementação de Memory<T>
e ReadOnlyMemory<T>
removem esta restrição. Use esses tipos para acessar diretamente buffers de memória.
Melhore o desempenho com segurança de referência
O uso desses recursos para melhorar o desempenho envolve estas tarefas:
-
Evite alocações: quando você altera um tipo de a
class
para umstruct
, você altera a forma como ele é armazenado. As variáveis locais são armazenadas na pilha. Os membros são armazenados em linha quando o objeto de contêiner é alocado. Essa mudança significa menos alocações e isso diminui o trabalho que o coletor de lixo faz. Também pode diminuir a pressão da memória para que o coletor de lixo seja executado com menos frequência. -
Preservar semântica de referência: alterar um tipo de a
class
para astruct
altera a semântica de passar uma variável para um método. O código que modificou o estado de seus parâmetros precisa ser modificado. Agora que o parâmetro é umstruct
, o método está modificando uma cópia do objeto original. Você pode restaurar a semântica original passando esse parâmetro como umref
parâmetro. Após essa alteração, o método modifica o originalstruct
novamente. -
Evite copiar dados: copiar tipos maiores
struct
pode afetar o desempenho em alguns caminhos de código. Você também pode adicionar oref
modificador para passar estruturas de dados maiores para métodos por referência em vez de por valor. -
Restringir modificações: Quando um
struct
tipo é passado por referência, o método chamado pode modificar o estado da estrutura. Você pode substituir o modificadorref
pelo modificadorref readonly
ouin
para indicar que o argumento não pode ser modificado. Prefiraref readonly
quando o método captura o parâmetro ou o retorna por referência apenas de leitura. Você também pode criarreadonly struct
tipos oustruct
tipos comreadonly
membros para ter um maior controle sobre quais membros de umstruct
podem ser modificados. -
Manipular diretamente a memória: Alguns algoritmos são mais eficientes ao tratar estruturas de dados como um bloco de memória contendo uma sequência de elementos. Os
Span
tipos eMemory
fornecem acesso seguro a blocos de memória.
Nenhuma dessas técnicas requer unsafe
código. Usado com sabedoria, você pode obter características de desempenho de código seguro que anteriormente só era possível usando técnicas inseguras. Você mesmo pode experimentar as técnicas no tutorial sobre como reduzir as alocações de memória.