Skip to main content

Span e Memory

  • Span

Stackalloc

  • Usada para alocar memória na stack (pilha) em tempo de execução. Diferente da alocação no heap (feita com new para objetos), stackalloc aloca arrays de tamanho fixo diretamente na pilha, sem precisar do garbage collector (GC). Quando a Thread é suspensa (await) esse valor some
danger

Não se pode usar em operações assíncronas

O que é Span<T> e Memory<T>?

Span<T> e Memory<T> são tipos do .NET criados para manipular dados de forma eficiente sem a necessidade de alocar ou copiar memória desnecessariamente. Ambos são projetados para trabalhar com fatias (slices) de memória, mas têm diferenças fundamentais que impactam como e onde podem ser usados.


Span<T>

  • Definição: Span<T> é uma estrutura que fornece uma visão contínua e tipada de uma região de memória.
  • Principais características:
    • Funciona diretamente na pilha (stack) ou em segmentos de memória fixa.
    • Altamente eficiente porque evita alocações no heap.
    • Útil para manipular buffers, arrays, ou blocos de memória sem criar cópias.
    • É um ref struct, o que significa que ele só pode existir na pilha e está sujeito a restrições específicas.

Exemplo básico:

Span<int> span = stackalloc int[5]; // Aloca um array na pilha
span[0] = 42;
Console.WriteLine(span[0]); // Saída: 42

Restrições de Span<T>:

  1. Não pode ser armazenado no heap:
    • Como Span<T> é um ref struct, ele não pode ser usado em capturas de closures, como lambdas, ou armazenado em variáveis que poderiam ser movidas para o heap.
  2. Não pode ser usado em operações assíncronas:
    • Isso ocorre porque as operações assíncronas podem suspender o contexto atual e mover o estado do método para o heap.
danger

O Span faz uma referência a certa parte do valor em memoria, então ele não aloca. Span faz referência a um valor alt text


Memory<T>

  • Definição: Memory<T> é um tipo que representa uma fatia de memória que pode residir tanto na pilha quanto no heap.
  • Principais características:
    • Ao contrário de Span<T>, Memory<T> não é um ref struct, permitindo que ele seja armazenado no heap.
    • É ideal para cenários onde você precisa usar fatias de memória em contextos assíncronos ou que precisam ser persistidos além do escopo imediato.
    • Oferece uma propriedade .Span para acesso direto aos dados.

Exemplo básico:

Memory<int> memory = new int[5]; // Aloca no heap
memory.Span[0] = 42;
Console.WriteLine(memory.Span[0]); // Saída: 42

Por que usar Memory<T>?

  • Compatível com operações assíncronas:
    • Como Memory<T> pode ser movido para o heap, ele é seguro para ser usado em contextos onde a execução pode ser suspensa.
  • Maior flexibilidade:
    • Embora seja menos eficiente que Span<T> (por permitir alocação no heap), ele é necessário para manipular dados em cenários mais complexos.

Por que Span<T> não pode ser usado em operações assíncronas?

1. Natureza da pilha

Span<T> é vinculado à pilha, que é uma área de memória de curta duração. Quando uma operação assíncrona é iniciada (como await), o estado do método precisa ser preservado para ser retomado depois que a operação for concluída. Para fazer isso, o compilador move o estado do método para o heap.

No entanto, como Span<T> não pode ser movido para o heap, ele não pode sobreviver ao ponto de suspensão. Isso resultaria em comportamentos indefinidos ou falhas.

Exemplo que causaria erro:

async Task ProcessData()
{
Span<int> data = stackalloc int[10]; // Aloca na pilha
await Task.Delay(1000); // Suspende o método
Console.WriteLine(data[0]); // ERRO: O Span não está mais válido
}

2. Captura de contexto

Operações assíncronas frequentemente precisam capturar variáveis locais em closures para garantir que elas estejam disponíveis após o retorno do método. Como Span<T> é um ref struct, ele não pode ser capturado.

Exemplo que também causa erro:

Span<int> data = stackalloc int[5];
Func<int> getFirstElement = () => data[0]; // ERRO: Captura de Span em closure

Quando usar Span<T> e Memory<T>?

Use Span<T> quando:

  • Você está trabalhando com operações síncronas.
  • Precisa de alto desempenho e quer evitar alocações no heap.
  • Está manipulando buffers ou blocos de memória que só precisam existir durante o escopo atual.

Use Memory<T> quando:

  • Você está trabalhando com operações assíncronas.
  • Precisa armazenar ou manipular dados que podem sobreviver além do escopo atual.
  • Quer a flexibilidade de usar fatias de memória em qualquer lugar, mesmo com custos ligeiramente maiores devido ao uso potencial do heap.

Resumo das Diferenças

CaracterísticaSpan<T>Memory<T>
Local de armazenamentoPilha (stack)Heap ou pilha (heap/stack)
Pode ser usado em async?NãoSim
Naturezaref structClasse com suporte a fatias
EficiênciaMais eficienteMenos eficiente
Cenário idealOperações síncronas rápidasOperações assíncronas ou longas

Exemplo Comparativo

Usando Span<T> (Síncrono):

void ProcessData()
{
Span<int> data = stackalloc int[5];
data[0] = 42;
Console.WriteLine(data[0]); // Saída: 42
}

Usando Memory<T> (Assíncrono):

async Task ProcessDataAsync()
{
Memory<int> data = new int[5];
data.Span[0] = 42;
await Task.Delay(1000); // Operação assíncrona
Console.WriteLine(data.Span[0]); // Saída: 42
}

Conclusão

  • Span<T> é extremamente eficiente para operações rápidas e locais à pilha, mas suas restrições o tornam incompatível com cenários assíncronos ou de longa duração.
  • Memory<T> é mais flexível, permitindo uso em contextos assíncronos ou quando é necessário armazenar dados no heap.