4 maneiras diferentes de criar um Array em C#
Neste artigo vou mostrar 4 maneiras diferentes para criar um array em C# e como essas maneiras se diferem uma das outras
Utilizando "new"
A primeira é bem óbvia, nós usamos o operador new
para criar um novo array:
var meuArray = new int[100];
Nós criamos um array com 100 elementos. O importante aqui é que "nós" tivemos que fazer o trabalho difícil de criar o array, mas por outro lado, nós não somos responsáveis por liberar a memória. O querido Garbage Collector (GC) faz isso por nós.
ArrayPool
Você pode imaginar um ArrayPool como uma carona de carro. Você vai compartilhar o array com outros. Quando você precisar de um array, você pode "aluga-lo" temporariamente para utilização, mas você tem que devolve-lo após o trabalho. Essa é a grande diferença entre criar um novo array com new
. A responsabilidade de limpar essa memória recai sobre você.
var meuArray = ArrayPool<int>.Shared.Rent(100);
// Faça seu trabalho
ArrayPool<int>.Shared.Return(meuArray);
Mas tenha certeza que você vai devolver o array, senão o ArrayPool
pode ficar totalmente ocupado, e por não ter mais recursos para emprestar ele terá que criar novos elementos no seu estoque, o que pode degradar sua performance. Outra coisa especial aqui é, que nós usamos a instância Shared
do ArrayPool
e o método Rent
. Seu único parâmetro é chamado minimumLenght
, que significa que seu array terá garantidamente o tamanho mínimo, mas pode também ser maior que o solicitado. Se você quiser saber mais sobre isso, leia este artigo.
GC.AllocateArray
GC.AllocateArray
é outra maneira de criar um array. Isto tem dois parâmetros: O Primeiro é o lenght
mais um booleano para indicar se o array deve ser pinned. Arrays marcados como Pinned significam que o GC não deve mover pela memória os blocos associados com o array. Isto é muito útil se você trabalha com recursos não gerenciados, de outra forma, não existem muitas razões para utiliza-lo.
De qualquer forma, você pode chegar ao mesmo resultado utilizando arrays "normais" com o termo fixed
. Aqui está como você poderia utilizar o GC.AllocateArray
:
var meuArray = GC.AllocateArray<int>(100);
GC.AllocateUninitializedArray
Esta maneira de criar um array através do AllocateUninitializedArray faz praticamente a mesma coisa que o GC.AllocateArray<int>
com um pequeno toque. Normalmente no .NET os arrays são inicializados com o valor padrão, então, se você tem algo como: var meuArray = new int[2]
; Então meuArray[0] == 0;
e meuArray[1] == 0
. Em termos de performance isso pode ser um pouco mais rápido do que a versão inicializada. De qualquer forma, você pode chegar ao mesmo resultado utilizando SkipLocalsInitAttribute
. Aqui está como você poderia utilizar o GC.AllocateUninitializedArray
:
var myArray = GC.AllocateUninitializedArray<int>(100);
Comparação
Vamos verificar como estas diferentes maneiras de criar array se comportam com tamanhos diferentes.
Aviso: Utilize new[]
na maioria dos casos. As outras opções são designadas para otimizações profundas e não para uso geral. Então teste seu caso de uso e decida mais tarde.
[MemoryDiagnoser]
public class ArrayBenchmark
{
[Params(10, 100, 1_000, 10_000, 100_000, 1_000_000)]
public int ArraySize { get; set; }
[Benchmark(Baseline = true)]
public int[] NewArray() => new int[ArraySize];
[Benchmark]
public int[] ArrayPoolRent() => ArrayPool<int>.Shared.Rent(ArraySize);
[Benchmark]
public int[] GCZeroInitialized() => GC.AllocateArray<int>(ArraySize);
[Benchmark]
public int[] GCZeroUninitialized() => GC.AllocateUninitializedArray<int>(ArraySize);
}
Resultados
| Metodo | ArraySize | Mean | Error | StdDev | Median | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------------- |---------- |-----------------:|---------------:|---------------:|-----------------:|------:|--------:|---------:|---------:|---------:|------------:|
| NewArray | 10 | 4.674 ns | 0.2270 ns | 0.6175 ns | 4.500 ns | 1.00 | 0.00 | 0.0153 | - | - | 64 B |
| ArrayPoolRent | 10 | 16.858 ns | 0.7670 ns | 2.2008 ns | 16.246 ns | 3.70 | 0.56 | 0.0210 | - | - | 88 B |
| GCZeroInitialized | 10 | 32.604 ns | 0.6481 ns | 0.5412 ns | 32.407 ns | 6.83 | 0.75 | 0.0153 | - | - | 64 B |
| GCZeroUninitialized | 10 | 5.170 ns | 0.3419 ns | 0.9588 ns | 4.884 ns | 1.12 | 0.27 | 0.0153 | - | - | 64 B |
| | | | | | | | | | | | |
| NewArray | 100 | 18.643 ns | 0.4770 ns | 1.3057 ns | 18.034 ns | 1.00 | 0.00 | 0.1014 | - | - | 424 B |
| ArrayPoolRent | 100 | 33.094 ns | 0.8942 ns | 2.4628 ns | 32.237 ns | 1.79 | 0.18 | 0.1281 | - | - | 536 B |
| GCZeroInitialized | 100 | 47.771 ns | 1.1578 ns | 3.3033 ns | 47.263 ns | 2.59 | 0.25 | 0.1013 | - | - | 424 B |
| GCZeroUninitialized | 100 | 18.287 ns | 0.4325 ns | 0.3611 ns | 18.164 ns | 0.98 | 0.08 | 0.1014 | - | - | 424 B |
| | | | | | | | | | | | |
| NewArray | 1000 | 156.640 ns | 2.9920 ns | 2.7987 ns | 157.671 ns | 1.00 | 0.00 | 0.9613 | - | - | 4,024 B |
| ArrayPoolRent | 1000 | 116.015 ns | 0.8502 ns | 0.7953 ns | 115.891 ns | 0.74 | 0.01 | 0.9813 | - | - | 4,120 B |
| GCZeroInitialized | 1000 | 186.634 ns | 3.8069 ns | 8.7471 ns | 185.534 ns | 1.24 | 0.05 | 0.9613 | - | - | 4,024 B |
| GCZeroUninitialized | 1000 | 111.039 ns | 2.3022 ns | 3.9712 ns | 110.275 ns | 0.71 | 0.03 | 0.9587 | - | - | 4,024 B |
| | | | | | | | | | | | |
| NewArray | 10000 | 1,450.991 ns | 28.9030 ns | 59.0412 ns | 1,454.230 ns | 1.00 | 0.00 | 9.5234 | 0.0019 | - | 40,024 B |
| ArrayPoolRent | 10000 | 678.110 ns | 13.5437 ns | 16.1228 ns | 681.002 ns | 0.47 | 0.02 | 15.6240 | 0.0010 | - | 65,560 B |
| GCZeroInitialized | 10000 | 1,352.078 ns | 20.4704 ns | 18.1465 ns | 1,349.767 ns | 0.93 | 0.05 | 9.5234 | 0.0019 | - | 40,024 B |
| GCZeroUninitialized | 10000 | 419.071 ns | 8.2441 ns | 10.1245 ns | 417.218 ns | 0.29 | 0.01 | 9.5234 | 0.0005 | - | 40,024 B |
| | | | | | | | | | | | |
| NewArray | 100000 | 22,770.844 ns | 531.6404 ns | 1,473.1730 ns | 22,217.963 ns | 1.00 | 0.00 | 124.9695 | 124.9695 | 124.9695 | 400,066 B |
| ArrayPoolRent | 100000 | 18,859.646 ns | 451.2290 ns | 1,287.3816 ns | 18,214.809 ns | 0.83 | 0.07 | 166.6565 | 166.6565 | 166.6565 | 524,317 B |
| GCZeroInitialized | 100000 | 22,818.945 ns | 456.0302 ns | 1,293.6817 ns | 22,739.250 ns | 1.01 | 0.08 | 124.9695 | 124.9695 | 124.9695 | 400,066 B |
| GCZeroUninitialized | 100000 | 13,473.716 ns | 436.1365 ns | 1,265.3112 ns | 13,172.961 ns | 0.60 | 0.07 | 124.9847 | 124.9847 | 124.9847 | 400,025 B |
| | | | | | | | | | | | |
| NewArray | 1000000 | 1,108,601.149 ns | 22,592.9715 ns | 18,866.1545 ns | 1,114,450.342 ns | 1.00 | 0.00 | 139.6484 | 139.4043 | 139.4043 | 4,000,068 B |
| ArrayPoolRent | 1000000 | 219,394.967 ns | 19,411.7449 ns | 57,235.9678 ns | 231,375.549 ns | 0.22 | 0.02 | 23.1934 | 23.1934 | 23.1934 | 4,194,329 B |
| GCZeroInitialized | 1000000 | 1,146,780.574 ns | 14,884.3974 ns | 13,922.8745 ns | 1,144,215.051 ns | 1.03 | 0.02 | 140.8691 | 140.6250 | 140.6250 | 4,000,070 B |
| GCZeroUninitialized | 1000000 | 190,029.061 ns | 16,728.2807 ns | 49,323.7129 ns | 200,659.369 ns | 0.15 | 0.05 | 22.7051 | 22.7051 | 22.7051 | 4,000,023 B |
Como você pode ver, o ArrayPool
usa um pouco mais de espaço do que o solicitado. Também para pequenos arrays, não há razão real para aplicar as alternativas abordadas. Novamente, por favor teste seu caso de uso primeiro e tenha cuidado com as consequências e as "novas" responsabilidades.
Recursos
- O Repositório da comparação pode ser encontrado aqui