Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Importante
As técnicas descritas nesta seção melhoram o desempenho quando aplicadas a caminhos quentes em seu código. Os caminhos frequentes são as seções da base de código executadas com frequência e repetidamente em operações normais. A aplicação dessas técnicas ao código que não é executado com frequência terá impacto mínimo. Antes de fazer alterações para melhorar o desempenho, é essencial 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 aplicativo na seção sobre Diagnóstico e instrumentação. Você pode praticar uma sessão de criação de perfil no tutorial para medir o uso da memória na documentação do Visual Studio.
Depois de medir o uso da memória e determinar que você pode reduzir as alocações, use as técnicas nesta seção para reduzir as alocações. Após cada alteração sucessiva, meça o uso da memória novamente. Verifique se cada alteração tem um impacto positivo no uso da memória em seu aplicativo.
O trabalho de otimização de desempenho no .NET geralmente significa remover alocações do seu código. Cada bloco de memória alocado deve eventualmente ser liberado. Menos alocações reduzem o tempo gasto na coleta de lixo. Permite um tempo de execução mais previsível ao remover coletas de lixo de caminhos de código específicos.
Para reduzir alocações, uma tática comum é alterar as estruturas de dados críticas de tipos class
para tipos struct
. Essa alteração afeta a semântica de usar esses tipos. Parâmetros e retornos agora são passados por valor em vez de por referência. O custo de copiar um valor será 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 para tipos maiores. Para combater o efeito da cópia, os desenvolvedores podem passar esses tipos por meio de ref
para 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 estruturas com ponteiros e memória não processada para alcançar o mesmo impacto no desempenho. O compilador gera um código verificávelmente seguro para os novos ref
recursos relacionados.
Código verificávelmente seguro significa que o compilador detecta possíveis sobrecargas de buffer ou acesso à memória não alocada ou liberada. O compilador detecta e impede alguns erros.
Transmitir e retornar por referência
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 tipos struct
, 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 a referência ao bloco de memória.
Em C#, os parâmetros para métodos são passados por valor e os valores retornados são retornados por valor. O valor do argumento é passado para o método. O valor do argumento de retorno é o valor retornado.
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 retornado é retornado por referência. Uma referência ao local de armazenamento é o valor retornado.
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 o local de 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. Ao atribuir referências a uma variável, você altera aquilo a que ela se refere.
Você pode trabalhar diretamente com o armazenamento para valores usando variáveis ref
, transmissão por referência e atribuição de ref. As regras de escopo impostas pelo compilador garantem a segurança ao trabalhar diretamente com o armazenamento.
Os modificadores ref readonly
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 como referência de somente leitura. Nesses casos, você deve usar o ref readonly
modificador. Caso contrário, o in
modificador oferecerá mais flexibilidade. Você não precisa adicionar o in
modificador a um argumento para um in
parâmetro, portanto, é possível atualizar as assinaturas de API existentes com segurança usando o in
modificador. O compilador emitirá um aviso se você não adicionar o modificador ref
ou in
a um argumento para um parâmetro ref readonly
.
Contexto ref seguro
O C# inclui regras para ref
expressões para garantir que uma ref
expressão não possa ser acessada quando o armazenamento ao qual 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 que está sendo referenciado. O contexto de ref safe define o escopo no qual uma ref
expressão é segura para acessar ou modificar. A tabela a seguir lista os contextos de ref safe para tipos de variáveis. Os campos ref
não podem ser declarados em um class
ou em uma struct
não ref, portanto, essas linhas não estão na tabela:
Declaração | contexto ref seguro |
---|---|
local não ref | bloco onde o local é declarado |
parâmetro não referencial | método atual |
Parâmetro ref , ref readonly , in |
método de chamada |
Parâmetro out |
método atual |
Campo de class |
método de chamada |
campo struct não ref |
método atual |
Campo ref de ref struct |
método de chamada |
Uma variável poderá ser ref
retornada se o contexto ref seguro for o método chamador. Se o contexto ref seguro for o método atual ou um bloco, o retorno de ref
não será permitido. O snippet a seguir mostra dois exemplos. Um campo membro pode ser acessado no escopo que chama um método, portanto, o contexto ref seguro de um campo de classe ou estrutura é o método de chamada. O contexto seguro de referência para um parâmetro com os modificadores ref
ou in
abrange todo o método. 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 modificador ref readonly
ou o modificador in
é aplicado a um parâmetro, esse parâmetro pode ser retornado por ref readonly
, não por ref
.
O compilador garante que uma referência não possa escapar de seu contexto ref safe. Você pode usar os parâmetros ref
, as variáveis locais ref return
e ref
com segurança porque o compilador detecta caso você tenha escrito acidentalmente um código onde uma expressão ref
poderia 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 eles possam ser usados com segurança. Um ref struct
tipo pode incluir ref
campos. Isso requer 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 em que todos os seus ref
campos podem ser acessados. Em outras palavras, é a interseção do contexto ref seguro de todos os campos ref
. O método a seguir retorna um ReadOnlySpan<char>
para um campo membro, portanto, o contexto seguro é o método:
private string longMessage = "This is a long message";
public ReadOnlySpan<char> Safe()
{
var span = longMessage.AsSpan();
return span;
}
Por outro lado, o código a seguir emite um erro porque o membro ref field
do Span<int>
refere-se a uma matriz de inteiros alocados na pilha. 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> fornece um modelo unificado para trabalhar com memória.
System.ReadOnlySpan<T> e System.ReadOnlyMemory<T> fornecem versões somente leitura para acessar a memória. Todos eles fornecem uma abstração em 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. Os intervalos contêm um ref field
. Portanto, instâncias de um intervalo não podem deixar o contexto seguro. O contexto seguro de um ref struct
é o contexto seguro de referência de sua ref field
. A implementação de Memory<T>
e ReadOnlyMemory<T>
remove essa restrição. Você usa esses tipos para acessar diretamente buffers de memória.
Melhorar o desempenho com segurança de ref
Usar esses recursos para melhorar o desempenho envolve estas tarefas:
-
Evite alocações: quando você altera um tipo de um
class
para umstruct
, você altera como ele é armazenado. As variáveis locais são armazenadas na pilha. Os membros são armazenados de forma integrada quando o objeto de contêiner é alocado. Essa alteração significa menos alocações e isso diminui o trabalho que o coletor de lixo faz. Também pode diminuir a pressão de memória, fazendo com que o coletor de lixo seja executado com menos frequência. -
Preservar semântica de referência: alterar um tipo de um
class
para umstruct
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 de modificação. 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 aos 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 do struct. Você pode substituir o modificadorref
pelos modificadoresref readonly
ouin
para indicar que o argumento não pode ser modificado. Prefiraref readonly
quando o método capta o parâmetro ou o retorna por referência somente leitura. Você também pode criar tiposreadonly struct
oustruct
com membrosreadonly
para fornecer mais 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 que contém uma sequência de elementos. Os tipos
Span
eMemory
fornecem acesso seguro a blocos de memória.
Nenhuma dessas técnicas exige unsafe
código. Usado com sabedoria, você pode obter características de desempenho do código seguro que antes só era possível usando técnicas não seguras. Você pode experimentar as técnicas por conta própria no tutorial sobre como reduzir as alocações de memória.