Benchmark de leitura e escrita de arquivos de imagem utilizando duas abordagens diferentes com Node.js: leitura direta (buffer) e stream.
Este projeto visa comparar o desempenho entre dois métodos de leitura e escrita de arquivos:
- 📦
read-file.ts
: usareadFile
ewriteFile
dofs/promises
, lendo e escrevendo o arquivo inteiro na memória (buffer). - 🌊
read-file-stream.ts
: usacreateReadStream
ecreateWriteStream
compipeline
para processar os arquivos por streaming.
O benchmark é realizado com a ferramenta Clinic.js.
Para realizar os testes, cole uma ou mais imagens na pasta input/
.
👉 Como sugestão, você pode baixar imagens grandes no site: visibleearth.nasa.gov
Este projeto utiliza o ImageMagick através do comando spawn
do Node.js para realizar operações de manipulação de imagem. Optou-se por essa abordagem para evitar o controle interno de memória que bibliotecas como Sharp possuem, permitindo um benchmark mais fiel do desempenho puro das operações de leitura e escrita via streaming.
Dessa forma, o fluxo do processo é: leitura do arquivo de imagem -> manipulação via ImageMagick executado em processo separado -> escrita do arquivo de saída.
Isso garante uma avaliação realista da performance do I/O sem interferência de otimizações internas específicas.
Antes de iniciar, é necessário compilar os arquivos TypeScript:
npm run build
npm run start-read-file
npm run start-read-file-stream
- Node.js >= 20.10.0
- npm
- Clinic.js (instalado via dependências do projeto)
input/
: arquivos de entrada.output/
: arquivos gerados após leitura e escrita.
A ferramenta Clinic.js Doctor foi utilizada para medir o impacto de cada abordagem no uso de memória durante o processamento dos arquivos de imagem.
Os testes foram realizados processando:
- 🖼️ 1 arquivo
.tif
(~30,5 MB) - 🖼️ 1 arquivo
.png
(~292,8 MB)
🔢 Volume total processado: ~323,3 MB
- 🧠 355 MB de uso de memória RSS
- Tempo de uso concentrado logo no início, com carga alta em pouco tempo
- 🧠 90 MB de uso de memória RSS (redução de ~75% em relação ao buffer)
- Comportamento muito mais estável e previsível
A abordagem com readFile
e writeFile
realiza a leitura e escrita do arquivo inteiro na memória, o que causa um alto consumo de memória RAM (como evidenciado pelo pico de 355 MB no teste com apenas dois arquivos). Esse método pode rapidamente se tornar um gargalo em aplicações que lidam com arquivos grandes ou múltiplos arquivos simultaneamente.
Por outro lado, a abordagem com createReadStream
e createWriteStream
utiliza streams, que carregam e processam os dados de forma incremental e contínua, sem precisar manter o arquivo inteiro na memória. Isso reduz drasticamente o uso de memória (cerca de 90 MB no mesmo cenário de teste) e torna o sistema muito mais eficiente.
As vantagens do uso de streams incluem:
- 🔁 Melhor gerenciamento de memória, evitando sobrecarga e gargalos
- 💸 Menor custo com infraestrutura, especialmente em servidores em nuvem com cobrança por uso de memória e CPU
- 🧱 Maior escalabilidade, permitindo processar múltiplos arquivos em paralelo sem comprometer o desempenho do sistema
- ⚙️ Mais estabilidade e previsibilidade, com uso consistente de recursos mesmo em cenários de carga elevada
✅ Sempre que possível, prefira streams para tarefas intensivas de I/O com arquivos, especialmente imagens, vídeos, grandes documentos ou qualquer dado binário pesado.
MIT © Bruno Menegidio