std.io Buffered — BufferedReader e BufferedWriter
Os tipos BufferedReader e BufferedWriter envolvem readers e writers existentes adicionando uma camada de buffering. Isso melhora significativamente o desempenho ao reduzir o número de syscalls para o sistema operacional, especialmente quando se faz muitas leituras ou escritas pequenas.
Visão Geral
Sem buffering, cada chamada a read() ou write() resulta diretamente em uma syscall ao SO. Com buffering, as operações acumulam dados em um buffer intermediário em memória do usuário, fazendo syscalls apenas quando o buffer está cheio (escrita) ou vazio (leitura).
const std = @import("std");
// Funções de conveniência para criar readers/writers com buffer
const buf_reader = std.io.bufferedReader(reader_original);
const buf_writer = std.io.bufferedWriter(writer_original);
Tipos Principais
BufferedReader
pub fn BufferedReader(comptime ReaderType: type) type
O tipo retornado por std.io.bufferedReader(). O tamanho padrão do buffer é 4096 bytes.
Métodos:
// Obtém o reader com buffer
pub fn reader(self: *Self) Reader
// Acessa o reader subjacente
pub fn unbuffered(self: *Self) ReaderType
BufferedWriter
pub fn BufferedWriter(comptime WriterType: type) type
O tipo retornado por std.io.bufferedWriter(). O tamanho padrão do buffer é 4096 bytes.
Métodos:
// Obtém o writer com buffer
pub fn writer(self: *Self) Writer
// Força a escrita de todos os dados pendentes no buffer
pub fn flush(self: *Self) !void
// Acessa o writer subjacente
pub fn unbuffered(self: *Self) WriterType
Versões com Tamanho Customizado
// Reader com buffer de tamanho customizado
pub fn BufferedReaderSize(comptime ReaderType: type, comptime N: usize) type
// Writer com buffer de tamanho customizado
pub fn BufferedWriterSize(comptime WriterType: type, comptime N: usize) type
// Funções de conveniência
pub fn bufferedReaderSize(comptime N: usize, reader: anytype) BufferedReaderSize(@TypeOf(reader), N)
pub fn bufferedWriterSize(comptime N: usize, writer: anytype) BufferedWriterSize(@TypeOf(writer), N)
Exemplo 1: Leitura Eficiente de Arquivo
Lendo um arquivo grande linha por linha com buffering:
const std = @import("std");
pub fn main() !void {
const arquivo = try std.fs.cwd().openFile("log_grande.txt", .{});
defer arquivo.close();
// Envolver o reader do arquivo com um buffer de 4096 bytes
var buf_reader = std.io.bufferedReader(arquivo.reader());
const reader = buf_reader.reader();
var contagem: usize = 0;
var contagem_erros: usize = 0;
var buf: [8192]u8 = undefined;
while (reader.readUntilDelimiterOrEof(&buf, '\n')) |maybe_linha| {
if (maybe_linha) |linha| {
contagem += 1;
if (std.mem.indexOf(u8, linha, "ERROR")) |_| {
contagem_erros += 1;
}
} else break;
} else |_| {}
std.debug.print("Linhas totais: {d}\n", .{contagem});
std.debug.print("Linhas com erro: {d}\n", .{contagem_erros});
}
Exemplo 2: Escrita Eficiente com BufferedWriter
Gerando um arquivo CSV grande com buffering:
const std = @import("std");
pub fn main() !void {
const arquivo = try std.fs.cwd().createFile("saida.csv", .{});
defer arquivo.close();
// Criar writer com buffer
var buf_writer = std.io.bufferedWriter(arquivo.writer());
const writer = buf_writer.writer();
// Cabeçalho
try writer.writeAll("id,nome,valor\n");
// Gerar 10000 linhas — o buffer reduz as syscalls de escrita
for (0..10000) |i| {
try writer.print("{d},item_{d},{d:.2}\n", .{
i,
i,
@as(f64, @floatFromInt(i)) * 1.5,
});
}
// IMPORTANTE: sempre fazer flush ao terminar!
try buf_writer.flush();
std.debug.print("Arquivo CSV gerado com sucesso.\n", .{});
}
Exemplo 3: Buffer de Tamanho Customizado
Para workloads específicos, ajustar o tamanho do buffer pode melhorar o desempenho:
const std = @import("std");
pub fn main() !void {
const arquivo = try std.fs.cwd().openFile("dados_binarios.dat", .{});
defer arquivo.close();
// Buffer de 64 KB para leitura de dados grandes
var buf_reader = std.io.bufferedReaderSize(64 * 1024, arquivo.reader());
const reader = buf_reader.reader();
// Ler dados em blocos
var total_bytes: usize = 0;
var temp_buf: [1024]u8 = undefined;
while (true) {
const n = try reader.read(&temp_buf);
if (n == 0) break;
total_bytes += n;
// Processar temp_buf[0..n]...
}
std.debug.print("Total de bytes lidos: {d}\n", .{total_bytes});
}
Padrões Comuns
Padrão de Uso Típico
O padrão mais usado é criar o buffered reader/writer logo após abrir o arquivo:
const arquivo = try std.fs.cwd().openFile("dados.txt", .{});
defer arquivo.close();
// Para leitura
var br = std.io.bufferedReader(arquivo.reader());
const reader = br.reader();
// Para escrita
var bw = std.io.bufferedWriter(arquivo.writer());
const writer = bw.writer();
defer bw.flush() catch {};
Sempre Fazer Flush
Ao usar BufferedWriter, dados podem permanecer no buffer após a última escrita. Sempre faça flush() antes de fechar o arquivo ou ao final da operação:
var bw = std.io.bufferedWriter(arquivo.writer());
const writer = bw.writer();
try writer.writeAll("dados importantes");
try bw.flush(); // Garante que tudo foi escrito no arquivo
Combinando com Funções Genéricas
Funções que aceitam anytype funcionam transparentemente com writers/readers com buffer:
fn processar(reader: anytype, writer: anytype) !void {
// Esta função funciona tanto com buffered quanto sem
var buf: [1024]u8 = undefined;
while (try reader.readUntilDelimiterOrEof(&buf, '\n')) |linha| {
try writer.print(">> {s}\n", .{linha});
}
}
Escolhendo o Tamanho do Buffer
- 4096 bytes (padrão): Bom para a maioria dos casos
- 8192-16384 bytes: Para leitura/escrita de arquivos moderados
- 65536+ bytes: Para processamento de arquivos grandes ou I/O de rede
- Menor que 4096: Raramente útil; apenas em situações com memória muito limitada
Considerações de Desempenho
O ganho de desempenho com buffering é particularmente notável quando:
- Muitas operações pequenas: Ler byte a byte ou escrever linhas curtas
- I/O de disco: Reduz o número de syscalls
read()/write() - I/O de rede: Reduz overhead de pacotes pequenos
Para leituras grandes e contíguas (ex: readAll de um bloco grande), o ganho é mínimo pois já há poucas syscalls.
Módulos Relacionados
- std.io — Visão geral do módulo de I/O
- std.io.Writer — Interface Writer
- std.io.Reader — Interface Reader
- std.io.FixedBufferStream — Stream sobre buffer em memória
- std.fs.File — Tipo File