O operador stackalloc permite que aloquemos arrays na stack ao invés da heap. Dessa forma, esses arrays são descartados automaticamente quando a execução de um método se encerra (no momento do retorno), não gerando pressão para o garbage collector , podendo melhorar a performance de forma muito siginificativa.
Veja também
- SÉRIE: Fundamentos para performance (em .NET)
- SÉRIE: Como o GC afeta a performance em .NET
- Entendendo a “Stack” em sua forma mais primitiva (em Assembly)
- Como variáveis locais são suportadas em Assembly
Evolução do operador stackalloc ao longo dos anos
No passado, a utilização desse operador implicava que o trecho de código onde ele aparecia fosse marcado como unsafe – o que restringia bastante seu uso.
public static unsafe string StackAllocReverseString(string input)
{
var reversedCharArray = stackalloc char[input.Length];
for (
int i = input.Length - 1, destIndex = 0;
i >= 0;
i--, destIndex++
)
{
reversedCharArray[destIndex] = input[i];
}
return new string(reversedCharArray);
}
Entretanto, a partir do C# 7.2, com o advento dos tipos System.Span<T> e ReadOnlySpan<T> isso não é mais necessário. No C# 8, o operador pode ser utilizado, inclusive, em expressões aninhadas.
int length = 1000; Span<byte> buffer = length <= 1024 ? stackalloc byte[length] : new byte[length];
O trecho de código acima mostra o nível de sofisticação atual dessa feature alocando de forma condicional o array na stack, se ele tiver menos do que 1024 posições, ou na heap.
Como stackalloc pode melhorar a performance do código
No passado, demonstramos como implementações ingênuas, que ignoram o impacto do GC, podem ter prejuízos significativos de performance. No código que segue, que valida CPFs, por exemplo, há muitas alocações desnecessárias.
public static bool ValidarCPF(string sourceCPF)
{
if (String.IsNullOrWhiteSpace(sourceCPF))
return false;
string clearCPF;
clearCPF = sourceCPF.Trim();
clearCPF = clearCPF.Replace("-", "");
clearCPF = clearCPF.Replace(".", "");
if (clearCPF.Length != 11)
{
return false;
}
int[] cpfArray;
int totalDigitoI = 0;
int totalDigitoII = 0;
int modI;
int modII;
if (clearCPF.Equals("00000000000") ||
clearCPF.Equals("11111111111") ||
clearCPF.Equals("22222222222") ||
clearCPF.Equals("33333333333") ||
clearCPF.Equals("44444444444") ||
clearCPF.Equals("55555555555") ||
clearCPF.Equals("66666666666") ||
clearCPF.Equals("77777777777") ||
clearCPF.Equals("88888888888") ||
clearCPF.Equals("99999999999"))
{
return false;
}
foreach (char c in clearCPF)
{
if (!char.IsNumber(c))
{
return false;
}
}
cpfArray = new int[11];
for (int i = 0; i < clearCPF.Length; i++)
{
cpfArray[i] = int.Parse(clearCPF[i].ToString());
}
for (int posicao = 0; posicao < cpfArray.Length - 2; posicao++)
{
totalDigitoI += cpfArray[posicao] * (10 - posicao);
totalDigitoII += cpfArray[posicao] * (11 - posicao);
}
modI = totalDigitoI % 11;
if (modI < 2) { modI = 0; }
else { modI = 11 - modI; }
if (cpfArray[9] != modI)
{
return false;
}
totalDigitoII += modI * 2;
modII = totalDigitoII % 11;
if (modII < 2) { modII = 0; }
else { modII = 11 - modII; }
if (cpfArray[10] != modII)
{
return false;
}
// CPF Válido!
return true;
}
Essas [tweet]alocações desnecessárias, em .NET, fatalmente, pressionam o GC fazendo com que a performance fique deteriorada.[/tweet] Na época, mostramos uma revisão desse código que eliminava todas as alocações na heap.
Na ocasião demonstramos, inclusive, como [tweet]código aparentemente menos eficiente, que realiza muito mais processamento, muitas vezes é compensado pela utilização mais racional da memória – sendo menos ofensivo ao GC.[/tweet]
public struct Cpf
{
private readonly string _value;
public readonly bool EhValido;
private Cpf(string value)
{
_value = value;
if (value == null)
{
EhValido = false;
return;
}
var posicao = 0;
var totalDigito1 = 0;
var totalDigito2 = 0;
var dv1 = 0;
var dv2 = 0;
bool digitosIdenticos = true;
var ultimoDigito = -1;
foreach (var c in value)
{
if (char.IsDigit(c))
{
var digito = c - '0';
if (posicao != 0 && ultimoDigito != digito)
{
digitosIdenticos = false;
}
ultimoDigito = digito;
if (posicao < 9)
{
totalDigito1 += digito * (10 - posicao);
totalDigito2 += digito * (11 - posicao);
}
else if (posicao == 9)
{
dv1 = digito;
}
else if (posicao == 10)
{
dv2 = digito;
}
posicao++;
}
}
if (posicao > 11)
{
EhValido = false;
return;
}
if (digitosIdenticos)
{
EhValido = false;
return;
}
var digito1 = totalDigito1 % 11;
digito1 = digito1 < 2
? 0
: 11 - digito1;
if (dv1 != digito1)
{
EhValido = false;
return;
}
totalDigito2 += digito1 * 2;
var digito2 = totalDigito2 % 11;
digito2 = digito2 < 2
? 0
: 11 - digito2;
EhValido = dv2 == digito2;
}
public static implicit operator Cpf(string value)
=> new Cpf(value);
public override string ToString() => _value;
}
O problema da implementação que recomendamos, de qualquer forma, é que ela é muito diferente daquela que faz alocações desncessárias. Além disso, sob diversas perspectivas, é também muito mais difícil de entender.
A utilização do operador stackalloc abre a possibilidade para que escrevamos uma versão, também sem alocações, porém muito mais próxima da que tínhamos inicialmente.
public static bool ValidarCPF(string sourceCPF)
{
static bool VerificaTodosValoresSaoIguais(ref Span<int> input)
{
for (var i = 1; i < 11; i++)
{
if (input[i] != input[0])
{
return false;
}
}
return true;
}
if (string.IsNullOrWhiteSpace(sourceCPF))
return false;
Span<int> cpfArray = stackalloc int[11];
var count = 0;
foreach (var c in sourceCPF)
{
if (char.IsDigit(c))
{
if (count > 10)
{
return false;
}
cpfArray[count] = c - '0';
count++;
}
}
if (count != 11) return false;
if (VerificaTodosValoresSaoIguais(ref cpfArray)) return false;
var totalDigitoI = 0;
var totalDigitoII = 0;
int modI;
int modII;
for (var posicao = 0; posicao < cpfArray.Length - 2; posicao++)
{
totalDigitoI += cpfArray[posicao] * (10 - posicao);
totalDigitoII += cpfArray[posicao] * (11 - posicao);
}
modI = totalDigitoI % 11;
if (modI < 2) { modI = 0; }
else { modI = 11 - modI; }
if (cpfArray[9] != modI)
{
return false;
}
totalDigitoII += modI * 2;
modII = totalDigitoII % 11;
if (modII < 2) { modII = 0; }
else { modII = 11 - modII; }
return cpfArray[10] == modII;
}
Aqui, também não ocorrem alocações e, consequentemente, não há pressão sobre o GC.

A nova versão, usando stackalloc, além de mais simples, é mais performática do que aquela com otimizações que apresentamos anteriormente.
Em tempo, as heurísticas de acionamento do GC melhoraram visivelmente nos últimos tempos fazendo com que o tempo de execução da implementação ingênua caísse pela metade (comparação com os resultados obtidos no post onde a experiência foi feita originalmente).
Muito obrigado pelo post. Sempre agregando algo de valor.