Otimizações de performance e memória no ZipArchive no .NET 10
O ZipArchive no .NET 10 recebeu uma série de melhorias de desempenho e de eficiência de memória, o que muda bastante a forma como as aplicações manipulam arquivos compactados. Neste artigo, vamos detalhar cada otimização, entender o que mudou por dentro da classe e analisar os números dos benchmarks oficiais.
O que mudou no ZipArchive no .NET 10
A Microsoft anunciou essas melhorias nas notas de lançamento do .NET 10 Preview 1, e elas seguiram para a versão final, lançada em novembro de 2025. Por isso, quem migra de versões anteriores ganha os benefícios sem alterar uma linha de código.
As otimizações vieram de duas contribuições da comunidade, as PRs #102704 e #103153. Ambas foram escritas por @edwardneal e afetam diretamente os modos Update e Extract, além da criação de arquivos ZIP.
Em resumo, o ZipArchive no .NET 10 entrega três ganhos principais:
- Modo Update: redução de 99,8% no tempo de execução e de 99,9996% no uso de memória ao adicionar um arquivo de 2 GB.
- Extração: cerca de 18% menos tempo e 18% menos memória.
- Criação de ZIPs: de 23% a 35% menos tempo, com consumo de memória cerca de 2% menor.
A seguir, vamos detalhar cada uma dessas otimizações e os seus impactos. Antes, porém, vale relembrar como a classe funciona.
Como o ZipArchive funciona
A classe ZipArchive fica no namespace System.IO.Compression e representa um pacote de arquivos compactados no formato ZIP. Junto com ZipFile e ZipArchiveEntry, ela forma o conjunto de APIs que o .NET oferece para esse formato.
Ao abrir um arquivo, você escolhe um dos três valores de ZipArchiveMode. Esses modos continuam os mesmos no ZipArchive no .NET 10.
Cada modo exige capacidades diferentes do stream, conforme a documentação oficial da classe ZipArchive:
- Read: o stream precisa suportar leitura.
- Create: o stream precisa suportar escrita.
- Update: o stream precisa suportar leitura, escrita e posicionamento (seek).
Além disso, todo arquivo ZIP termina com um diretório central, que lista as entradas e as posições delas dentro do arquivo. Esse detalhe é importante, porque explica por que atualizar um ZIP sempre foi uma operação cara.
O exemplo abaixo mostra o uso clássico do modo Update para adicionar uma entrada nova a um arquivo existente.
using System.IO.Compression;
using FileStream zipStream = new("release.zip", FileMode.Open);
using ZipArchive archive = new(zipStream, ZipArchiveMode.Update);
// cria uma nova entrada no fim do arquivo
ZipArchiveEntry readmeEntry = archive.CreateEntry("Readme.txt");
using StreamWriter writer = new(readmeEntry.Open());
writer.WriteLine("Information about this package.");
Esse código continua idêntico com o ZipArchive no .NET 10. A diferença está no que acontece por baixo dos panos quando o ZipArchive é descartado e grava as alterações.
Modo Update do ZipArchive no .NET 10
A PR #102704 otimiza a maneira como as entradas são gravadas dentro de um ZipArchive no modo Update. Essa foi, de longe, a mudança com o maior impacto nos números.
O problema nas versões anteriores
Antes dessa melhoria, o runtime carregava todas as entradas na memória e reescrevia o arquivo inteiro ao final. Consequentemente, o consumo de RAM ficava alto e o tempo de execução se degradava conforme o arquivo crescia.
Na prática, adicionar um pequeno arquivo a um ZIP de 2 GB exigia ler e regravar os 2 GB. Foi exatamente esse cenário que o ZipArchive no .NET 10 atacou.
Mesmo abrir e fechar o arquivo sem nenhuma alteração podia disparar escritas desnecessárias.
Como a nova implementação funciona
Com a otimização, o ZipArchive no .NET 10 deixou de carregar todas as entradas ao mesmo tempo. Segundo a descrição da PR, a nova implementação segue algumas regras simples:
- Mantém as entradas ordenadas pela posição (offset) em que aparecem no arquivo.
- Registra quais entradas mudaram e diferencia pequenas alterações de metadados de reescritas completas.
- Carrega uma entrada na memória apenas se os dados dela mudaram ou se uma entrada anterior precisa ser reescrita.
- Pula as entradas que não mudaram, em vez de regravá-las.
- Reduz o tamanho do stream ao final, eliminando o espaço que sobrou.
Dessa forma, acrescentar uma entrada no fim do arquivo exige apenas gravar a nova entrada e o diretório central. Por isso, o custo deixa de depender do tamanho total do ZIP.
Benchmarks do ZipArchive no .NET 10 no modo Update
As notas de lançamento trazem os seguintes números ao adicionar um arquivo de 2 GB a um ZIP existente:
- Redução de 99,8% no tempo de execução.
- Redução de 99,9996% no uso de memória.
| Versão | Tempo de execução | Memória alocada |
|---|---|---|
| .NET 9 | 4,187 s | 2 GB |
| .NET 10 | 9,452 ms | 7,01 KB |
Esses números mostram que a nova implementação praticamente eliminou o uso excessivo de memória. Em outras palavras, a operação ficou cerca de 442 vezes mais rápida no cenário medido.
Na própria PR, o autor publicou uma medição semelhante em outra máquina, com 5,128 s antes e 8,169 ms depois. Assim, a ordem de grandeza da melhoria se repete em ambientes diferentes.
Limites da otimização
Apesar dos números, o ganho varia conforme a posição da entrada alterada. Adicionar entradas novas ou modificar as últimas do arquivo traz o maior benefício.
Por outro lado, alterar uma das primeiras entradas obriga o runtime a reescrever tudo o que vem depois dela. Nesse caso, o desempenho fica próximo ao da implementação anterior.
Portanto, ao planejar o formato dos seus arquivos, prefira acrescentar entradas no fim. Assim, você aproveita ao máximo o ZipArchive no .NET 10.
A PR resolveu duas issues antigas do repositório: a de anexar entradas com pouco custo (#49149) e a de evitar escritas quando nada mudou (#34506). Além disso, os mantenedores decidiram entregar a mudança apenas no .NET 10, para que ela tivesse tempo de amadurecer antes do lançamento.
Extração com o ZipArchive no .NET 10
A PR #103153 melhora o desempenho da leitura e da extração de arquivos compactados. As notas de lançamento descrevem a mudança como uma paralelização da extração, somada à otimização das estruturas internas de dados.
Com isso, a PR resolve gargalos de desempenho e de consumo de memória, especialmente ao lidar com grandes volumes de arquivos. Também neste caso, o ZipArchive no .NET 10 entrega o ganho sem exigir mudanças no seu código.
O que mudou por dentro
A descrição técnica da PR mostra que o trabalho removeu o uso de BinaryReader e BinaryWriter de dentro de ZipArchive e ZipArchiveEntry. No lugar deles, a classe passou a ler e gravar diretamente no stream, em blocos de 4 KB.
Antes, o código gravava cada campo do cabeçalho separadamente, o que gerava cerca de 24 escritas individuais por arquivo. Além disso, a busca pelo registro de fim do diretório central (EOCD) passou a ler blocos maiores e usar instruções SIMD para localizar a assinatura.
Essa refatoração também removeu dependências que bloqueavam o suporte assíncrono. Ou seja, ela abriu caminho para as novas APIs assíncronas que vamos ver mais adiante.
Benchmarks de extração
Os ganhos de extração ficaram assim:
- Redução de cerca de 18% no tempo de execução.
- Redução de cerca de 18% no uso de memória.
| Arquivos extraídos | .NET 9 (tempo) | .NET 10 (tempo) | Diferença |
|---|---|---|---|
| 10 | 4.205,5 ns | 3.467,5 ns | 18% menor |
| 100 | 46.986,2 ns | 37.767,2 ns | 19% menor |
| 1.000 | 510.114,6 ns | 404.349,3 ns | 20% menor |
| 10.000 | 13.950.239,9 ns | 10.911.298,0 ns | 22% menor |
Repare que o ganho cresce junto com a quantidade de entradas. Portanto, quanto maior o arquivo, mais o ZipArchive no .NET 10 se destaca na leitura.
Segundo a PR, em cenários com latência de rede ou de disco, a melhoria na leitura chega a ser ainda maior. Isso acontece porque o código faz muito menos leituras pequenas no stream.
Criação de arquivos com o ZipArchive no .NET 10
A criação de um novo arquivo também ficou mais rápida, com redução de 23% a 35% no tempo de execução. Além disso, o consumo de memória caiu levemente, com cerca de 2% de redução.
| Arquivos criados | .NET 9 (tempo) | .NET 10 (tempo) | Diferença |
|---|---|---|---|
| 10 | 10,341 μs | 7,937 μs | 23% menor |
| 50 | 67,420 μs | 41,443 μs | 35% menor |
| 100 | 115,159 μs | 94,148 μs | 18% menor |
No ZipArchive no .NET 10, esse ganho vem principalmente do fim das escritas campo a campo. Afinal, gravar o cabeçalho de uma só vez reduz o número de chamadas ao stream.
O código de criação, mais uma vez, não precisa de nenhuma alteração. O exemplo abaixo cria um ZIP a partir de uma pasta, exatamente como em versões anteriores.
using System.IO.Compression;
// compacta todos os arquivos da pasta reports
ZipFile.CreateFromDirectory("reports", "reports.zip");
Novas APIs assíncronas do ZipArchive no .NET 10
Além das otimizações das PRs citadas, a versão final do ZipArchive no .NET 10 trouxe APIs assíncronas, um pedido antigo da comunidade. Segundo a página de novidades das bibliotecas do .NET 10, os principais métodos são:
ZipArchive.CreateAsync, para abrir ou criar um arquivo de forma assíncrona.ZipArchiveEntry.OpenAsync, para abrir o stream de uma entrada.ZipFile.CreateFromDirectoryAsync, para compactar uma pasta.ZipFile.ExtractToDirectoryAsync, para extrair um arquivo inteiro.
Com elas, o ZipArchive no .NET 10 evita o bloqueio de threads em operações de entrada e saída. Por isso, elas combinam muito bem com APIs web e serviços que processam vários arquivos ao mesmo tempo.
using System.IO.Compression;
// extrai o arquivo sem bloquear a thread atual
await ZipFile.ExtractToDirectoryAsync(
"backup.zip",
"restored",
overwriteFiles: false,
cancellationToken: cancellationToken);
Como ZipArchive implementa IAsyncDisposable, você também pode usar await using ao trabalhar diretamente com a classe. Assim, a gravação final do diretório central acontece de forma assíncrona.
Boas práticas com o ZipArchive no .NET 10
Mesmo com as melhorias, o modo Update ainda carrega na memória o conteúdo descompactado de uma entrada quando você chama Open nela. Por isso, o guia de boas práticas para ZIP e TAR no .NET recomenda o modo Update apenas para arquivos confiáveis e pequenos.
Para modificar arquivos grandes com o ZipArchive no .NET 10, a recomendação é abrir o original no modo Read e criar um novo arquivo no modo Create. Em seguida, você copia apenas as entradas desejadas.
Além disso, o guia lista cuidados importantes ao processar arquivos de origem desconhecida:
- Valide o caminho de destino de cada entrada para evitar ataques de path traversal.
- Limite o tamanho descompactado e a quantidade de entradas, para se proteger de ZIP bombs.
- Não compartilhe a mesma instância de
ZipArchiveentre threads. - Descarte sempre o
ZipArchivee os streams associados.
Se você também lida com arquivos comuns no dia a dia, vale ler o nosso artigo sobre manipulação de arquivos com C#. Afinal, boa parte dessas boas práticas também se aplica a eles.
Quem se beneficia das melhorias
As melhorias tornam o .NET 10 uma versão muito mais poderosa para manipular arquivos compactados. Na prática, aplicações que lidam com grandes volumes de dados sentem a diferença do ZipArchive no .NET 10 de imediato.
Por exemplo, sistemas de backup que acrescentam arquivos a um ZIP existente deixam de reescrever gigabytes a cada execução. Da mesma forma, serviços que geram pacotes para download ganham tempo na criação.
Também se beneficiam ferramentas que leem pacotes baseados em ZIP, como arquivos de Office e pacotes NuGet. Nesse caso, a leitura mais rápida do diretório central reduz o tempo de abertura.
O .NET 10 trouxe outras melhorias de desempenho no runtime, como as mudanças no Garbage Collector do .NET 10. Juntas, essas mudanças e o novo ZipArchive no .NET 10 reforçam o foco da versão em eficiência.
Se você ainda não conhece as demais novidades daquela primeira prévia, confira também o nosso resumo do .NET 10 Preview 1.
Conclusão
As otimizações do ZipArchive no .NET 10 representam um salto significativo na eficiência da manipulação de arquivos ZIP. Em suma, elas eliminam gargalos de desempenho e o consumo excessivo de memória.
- Modo Update: 99,8% mais rápido e 99,9996% mais eficiente em memória.
- Extração: cerca de 18% mais rápida e mais econômica em memória.
- Criação de ZIPs: de 23% a 35% mais rápida.
Se você utiliza o ZipArchive no .NET 10, basta atualizar o target framework para colher esses ganhos. Além disso, as novas APIs assíncronas deixam o código mais preparado para cenários de alta concorrência.
FAQ
Preciso mudar meu código para ter as melhorias do ZipArchive?
Não. As otimizações são internas, então basta compilar e executar a aplicação no .NET 10.
Por que o modo Update ficou tão mais rápido?
Porque a nova implementação reescreve apenas as entradas alteradas e as que vêm depois delas, em vez de carregar e regravar o arquivo inteiro.
Modificar a primeira entrada de um ZIP também ficou mais rápido?
Não muito. Nesse caso, todas as entradas seguintes precisam ser reescritas, e o desempenho fica próximo ao das versões anteriores.
Quem implementou essas otimizações?
As PRs #102704 e #103153 foram contribuições de @edwardneal ao repositório dotnet/runtime.
O .NET 10 tem APIs assíncronas para ZIP?
Sim. A versão final trouxe métodos como ZipFile.ExtractToDirectoryAsync, ZipFile.CreateFromDirectoryAsync e ZipArchiveEntry.OpenAsync.