Uso prático da palavra-chave `stackalloc`

134

Alguém já usou stackallocna programação em C #? Estou ciente do que faz, mas a única vez que aparece no meu código é por acidente, porque o Intellisense sugere isso quando começo a digitar static, por exemplo.

Embora não esteja relacionado aos cenários de uso stackalloc, na verdade, eu faço uma quantidade considerável de interoperabilidade herdada em meus aplicativos, para que de vez em quando eu pudesse recorrer ao uso de unsafecódigo. Mas, no entanto, costumo encontrar maneiras de evitar unsafecompletamente.

E como o tamanho da pilha de um único thread no .Net é de ~ 1Mb (me corrija se estiver errado), sou ainda mais reservado stackalloc.

Existem casos práticos em que se pode dizer: "essa é exatamente a quantidade certa de dados e processamento para eu ficar insegura e usar stackalloc"?

Groo
fonte
5
notei que System.Numbersusa muito referencesource.microsoft.com/#mscorlib/system/...
Slai

Respostas:

150

O único motivo para usar stackallocé o desempenho (para cálculos ou interoperabilidade). Ao usar em stackallocvez de uma matriz alocada ao heap, você cria menos pressão do GC (o GC precisa executar menos), não é necessário fixar as matrizes, é mais rápido alocar do que uma matriz de heap e é automaticamente liberado no método exit (matrizes de heap alocadas somente são desalocadas quando o GC é executado). Além disso, ao stackallocinvés de usar um alocador nativo (como malloc ou o equivalente .Net), você também ganha velocidade e desalocação automática na saída do escopo.

Em termos de desempenho, se você usar stackalloc, aumenta bastante a chance de acertos de cache na CPU devido à localidade dos dados.

Pop Catalin
fonte
26
Localidade dos dados, bom ponto! É isso que a memória gerenciada raramente alcançará quando você deseja alocar várias estruturas ou matrizes. Obrigado!
Groo
22
As alocações de heap geralmente são mais rápidas para objetos gerenciados do que para não gerenciados, porque não há lista livre para percorrer; o CLR apenas incrementa o ponteiro da pilha. Quanto à localidade, é mais provável que as alocações sequenciais acabem colocadas para processos gerenciados de longa execução devido à compactação de heap.
Shea
1
"é mais rápido alocar do que uma matriz de heap" Por que isso? Apenas localidade? De qualquer maneira, é apenas uma colisão de ponteiros, não?
precisa saber é o seguinte
2
@MaxBarraclough Porque você adiciona o custo do GC para acumular alocações ao longo da vida útil do aplicativo. Custo total de alocação = alocação + desalocação, neste caso, bump de ponteiro + GC Heap, vs bump de ponteiro + decremento de ponteiro Stack
Pop Catalin
35

Eu usei o stackalloc para alocar buffers para o trabalho DSP [quase] em tempo real. Foi um caso muito específico em que o desempenho precisava ser o mais consistente possível. Observe que há uma diferença entre consistência e taxa de transferência geral - nesse caso, eu não estava preocupado com as alocações de heap sendo muito lentas, apenas com o não determinismo da coleta de lixo naquele ponto do programa. Eu não o usaria em 99% dos casos.

Jim Arnold
fonte
25

stackallocé relevante apenas para código não seguro. Para código gerenciado, você não pode decidir onde alocar dados. Os tipos de valor são alocados na pilha por padrão (a menos que façam parte de um tipo de referência; nesse caso, eles são alocados no heap). Os tipos de referência são alocados no heap.

O tamanho da pilha padrão para um aplicativo .NET simples baunilha é de 1 MB, mas você pode alterar isso no cabeçalho do PE. Se você estiver iniciando threads explicitamente, também poderá definir um tamanho diferente através da sobrecarga do construtor. Para aplicativos ASP.NET, o tamanho da pilha padrão é de apenas 256K, o que deve ser lembrado se você alternar entre os dois ambientes.

Brian Rasmussen
fonte
É possível alterar o tamanho da pilha padrão do Visual Studio?
configurator
@ configurador: Não tanto quanto eu sei.
Brian Rasmussen
17

Inicialização Stackalloc de vãos. Nas versões anteriores do C #, o resultado do stackalloc só podia ser armazenado em uma variável local do ponteiro. A partir do C # 7.2, o stackalloc agora pode ser usado como parte de uma expressão e pode ser direcionado a uma extensão, e isso pode ser feito sem o uso da palavra-chave não segura. Assim, em vez de escrever

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}

Você pode escrever simplesmente:

Span<byte> bytes = stackalloc byte[length];

Isso também é extremamente útil em situações em que você precisa de algum espaço temporário para executar uma operação, mas deseja evitar a alocação de memória heap para tamanhos relativamente pequenos

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

Fonte: C # - Tudo sobre o Span: explorando uma nova base do .NET

anth
fonte
4
Obrigado pela dica. Parece que cada nova versão do C # se aproxima um pouco do C ++, o que é realmente uma coisa boa no IMHO.
Groo
1
Como pode ser visto aqui e aqui , Spaninfelizmente não está disponível no .NET framework 4.7.2 e nem mesmo no 4.8 ... Portanto, o novo recurso de idioma ainda é de uso limitado no momento.
Frederic
2

Existem ótimas respostas nesta pergunta, mas eu só quero ressaltar que

Stackalloc também pode ser usado para chamar APIs nativas

Muitas funções nativas exigem que o chamador aloque um buffer para obter o resultado do retorno. Por exemplo, a função CfGetPlaceholderInfocfapi.h possui a seguinte assinatura.

HRESULT CfGetPlaceholderInfo(
HANDLE                    FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID                     InfoBuffer,
DWORD                     InfoBufferLength,
PDWORD                    ReturnedLength);

Para chamá-lo em C # por meio de interoperabilidade,

[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);

Você pode fazer uso do stackalloc.

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
fjch1997
fonte