Buffer std :: fstream vs buffer manual (por que 10x ganho com buffer manual)?

Eu testei duas configurações de escrita:

1) buffer de Fstream:

// Initialization const unsigned int length = 8192; char buffer[length]; std::ofstream stream; stream.rdbuf()->pubsetbuf(buffer, length); stream.open("test.dat", std::ios::binary | std::ios::trunc) // To write I use : stream.write(reinterpret_cast(&x), sizeof(x)); 

2) buffer manual:

 // Initialization const unsigned int length = 8192; char buffer[length]; std::ofstream stream("test.dat", std::ios::binary | std::ios::trunc); // Then I put manually the data in the buffer // To write I use : stream.write(buffer, length); 

Eu esperava o mesmo resultado …

Mas meu buffer manual melhora o desempenho por um fator de 10 para gravar um arquivo de 100MB, e o buffer de fstream não muda nada em comparação com a situação normal (sem redefinir um buffer).

Alguém tem uma explicação desta situação?

EDIT: Aqui estão as notícias: um benchmark acabado de fazer em um supercomputador (arquitetura linux de 64 bits, dura intel Xeon 8-core, sistema de arquivos Lustre e … esperançosamente compiladores bem configurados) referência (e eu não explico o motivo da “ressonância” para um buffer manual de 1kB …)

EDIT 2: E a ressonância em 1024 B (se alguém tem uma idéia sobre isso, estou interessado): insira a descrição da imagem aqui

Isto é basicamente devido a sobrecarga de chamada de function e indirecção. O método ofstream :: write () é herdado do ostream. Essa function não está embutida no libstdc ++, que é a primeira fonte de sobrecarga. Então ostream :: write () tem que chamar rdbuf () -> sputn () para fazer a escrita atual, que é uma chamada de function virtual.

Além disso, libstdc ++ redireciona sputn () para outra function virtual xsputn () que adiciona outra chamada de function virtual.

Se você colocar os caracteres no buffer, pode evitar essa sobrecarga.

Eu gostaria de explicar qual é a causa do pico no segundo gráfico

Na verdade, as funções virtuais usadas pelo std::ofstream levam à performace decrescente como vemos na primeira imagem, mas não dá uma resposta porque a performance mais alta era quando o tamanho do buffer manual era menor que 1024 bytes.

O problema está relacionado ao alto custo de writev() e write() ) system call e implementação interna da class std::filebuf internal de std::ofstream .

Para mostrar como o write() influencia o desempenho, fiz um teste simples usando a ferramenta dd em minha máquina Linux para copiar arquivos de 10MB com diferentes tamanhos de buffer (opção bs):

 test@test$ time dd if=/dev/zero of=zero bs=256 count=40000 40000+0 records in 40000+0 records out 10240000 bytes (10 MB) copied, 2.36589 s, 4.3 MB/s real 0m2.370s user 0m0.000s sys 0m0.952s test$test: time dd if=/dev/zero of=zero bs=512 count=20000 20000+0 records in 20000+0 records out 10240000 bytes (10 MB) copied, 1.31708 s, 7.8 MB/s real 0m1.324s user 0m0.000s sys 0m0.476s test@test: time dd if=/dev/zero of=zero bs=1024 count=10000 10000+0 records in 10000+0 records out 10240000 bytes (10 MB) copied, 0.792634 s, 12.9 MB/s real 0m0.798s user 0m0.008s sys 0m0.236s test@test: time dd if=/dev/zero of=zero bs=4096 count=2500 2500+0 records in 2500+0 records out 10240000 bytes (10 MB) copied, 0.274074 s, 37.4 MB/s real 0m0.293s user 0m0.000s sys 0m0.064s 

Como você pode ver que quanto menos buffer, menor a velocidade de gravação e quanto tempo o dd gasta no espaço do sistema. Portanto, a velocidade de leitura / gravação diminui quando o tamanho do buffer diminui.

Mas por que a velocidade mais alta foi quando o tamanho do buffer manual foi inferior a 1024 bytes nos testes de buffer manual do criador de tópico? Por que isso foi quase constante?

A explicação está relacionada à implementação std::ofstream , especialmente ao std::basic_filebuf .

Por padrão, ele usa buffer de 1024 bytes (variável BUFSIZ). Então, quando você escreve seus dados usando peaces menores que 1024, a chamada de sistema writev() (not write() ) é chamada pelo menos uma vez para duas operações ofstream::write() (peaces têm tamanho de 1023 <1024 - primeiro é escrito para o buffer, e segunda força de escrita de primeiro e segundo). Com base nisso, podemos concluir que a velocidade ofstream::write() não depende do tamanho do buffer manual antes do pico ( write() é chamado pelo menos duas vezes raramente).

Quando você tenta escrever maior ou igual ao buffer de 1024 bytes de uma vez usando a chamada ofstream::write() , a chamada do sistema writev() é chamada para cada ofstream::write . Então, você vê que a velocidade aumenta quando o buffer manual é maior que 1024 (após o pico).

Além disso, se você gostaria de definir buffer std::ofstream maior que 1024 buffer (por exemplo, 8192 bytes buffer) usando streambuf::pubsetbuf() e chamar ostream::write() para gravar dados usando pedaços de tamanho 1024, você ficaria surpreso que a velocidade de gravação será a mesma que você usará o buffer 1024. É porque a implementação de std::basic_filebuf – a class interna de std::ofstream – é codificada para forçar a chamada do sistema writev() para cada chamada ofstream::write() quando o buffer passado é maior ou igual a 1024 bytes ( veja o código fonte basic_filebuf :: xsputn ( ). Há também um problema em aberto no bugzilla do GCC, que foi relatado em 2014-11-05 .

Então, a solução desse problema pode ser feita usando dois casos possíveis:

  • substitua std::filebuf por sua própria class e redefina std::ofstream
  • devide um buffer, que deve ser passado para o ofstream::write() , para os pedaços menores que 1024 e passá-los para o ofstream::write() um por um
  • não passe pequenos pedaços de dados para o ofstream::write() para evitar a diminuição do desempenho nas funções virtuais do std::ofstream

Gostaria de acrescentar às respostas existentes que esse comportamento de desempenho (toda a sobrecarga do método virtual chama / indireto) normalmente não é um problema se estiver escrevendo grandes blocos de dados. O que parece ter sido omitido da pergunta e essas respostas anteriores (embora provavelmente implicitamente compreendidas) é que o código original estava escrevendo um pequeno número de bytes a cada vez. Apenas para esclarecer para os outros: se você estiver escrevendo grandes blocos de dados (~ kB +), não há razão para esperar que o buffer manualmente tenha uma diferença significativa de desempenho no uso do buffering do std::fstream .