Estou tentando gravar grandes quantidades de dados no meu SSD (solid state drive). E por grandes quantidades, quero dizer 80GB.
Naveguei na Web em busca de soluções, mas o melhor que surgiu foi o seguinte:
#include <fstream>
const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
std::fstream myfile;
myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
//Here would be some error handling
for(int i = 0; i < 32; ++i){
//Some calculations to fill a[]
myfile.write((char*)&a,size*sizeof(unsigned long long));
}
myfile.close();
}
Compilado com o Visual Studio 2010 e otimizações completas e executado no Windows7, esse programa atinge o máximo de 20 MB / s. O que realmente me incomoda é que o Windows pode copiar arquivos de um outro SSD para esse SSD em algo entre 150 MB / se 200 MB / s. Então, pelo menos 7 vezes mais rápido. É por isso que acho que devo poder ir mais rápido.
Alguma idéia de como posso acelerar minha redação?
c++
performance
optimization
file-io
io
Dominic Hofer
fonte
fonte
fwrite()
eu poderia obter cerca de 80% das velocidades de gravação de pico. Somente comFILE_FLAG_NO_BUFFERING
eu consegui velocidade máxima.Respostas:
Isso fez o trabalho (no ano de 2012):
Acabei de cronometrar 8 GB em 36 segundos, que é de cerca de 220 MB / se acho que isso maximiza meu SSD. Vale ressaltar também que o código na pergunta usou um núcleo 100%, enquanto esse código usa apenas 2-5%.
Muito obrigado a todos.
Atualização : 5 anos se passaram é 2017 agora. Compiladores, hardware, bibliotecas e meus requisitos foram alterados. Foi por isso que fiz algumas alterações no código e fiz algumas novas medições.
Primeiro o código:
Esse código é compilado com o Visual Studio 2017 e o g ++ 7.2.0 (um novo requisito). Eu executei o código com duas configurações:
O que forneceu as seguintes medidas (após a redução dos valores de 1 MB, por serem óbvios outliers): Ambas as vezes a opção 1 e a opção 3 maximizam meu SSD. Eu não esperava que isso acontecesse, porque a opção 2 costumava ser o código mais rápido na minha máquina antiga naquela época.
TL; DR : Minhas medidas indicam que o uso
std::fstream
acabouFILE
.fonte
FILE*
é mais rápido que fluxos. Eu não esperava essa diferença, já que "deveria" ter sido ligada à E / S de qualquer maneira.ios::sync_with_stdio(false);
faz alguma diferença para o código com fluxo? Só estou curioso para saber a diferença entre usar essa linha e não, mas não tenho o disco rápido o suficiente para verificar a caixa da esquina. E se houver alguma diferença real.Tente o seguinte, em ordem:
Tamanho menor do buffer. Escrever ~ 2 MiB por vez pode ser um bom começo. No meu último laptop, ~ 512 KiB foi o ponto ideal, mas ainda não testei no meu SSD.
Nota: Observei que buffers muito grandes tendem a diminuir o desempenho. Notei perdas de velocidade com o uso de buffers de 16 MiB em vez de buffers de 512 KiB antes.
Use
_open
(ou_topen
se você deseja corrigir o Windows) para abrir o arquivo e use_write
. Isso provavelmente evitará muito buffer, mas não é certo.Usando funções específicas do Windows como
CreateFile
eWriteFile
. Isso evitará qualquer buffer na biblioteca padrão.fonte
FILE_FLAG_NO_BUFFERING
- no qual buffers maiores tendem a ser melhores. Desde que eu acho queFILE_FLAG_NO_BUFFERING
é praticamente DMA.Não vejo diferença entre std :: stream / FILE / device. Entre buffer e não buffer.
Observe também:
Estou vendo o código executado em 63 segundos.
Assim, uma taxa de transferência de: 260M / s (meu SSD parece um pouco mais rápido que o seu).
Não recebo nenhum aumento movendo para FILE * do std :: fstream.
Portanto, o fluxo C ++ está funcionando tão rápido quanto a biblioteca subjacente permitirá.
Mas acho injusto comparar o sistema operacional com um aplicativo que é construído no topo do sistema operacional. O aplicativo não pode fazer suposições (ele não sabe que as unidades são SSD) e, portanto, usa os mecanismos de arquivo do sistema operacional para transferência.
Enquanto o sistema operacional não precisa fazer nenhuma suposição. Ele pode dizer os tipos de unidades envolvidas e usar a técnica ideal para transferir os dados. Nesse caso, uma transferência direta de memória para memória. Tente escrever um programa que copie 80G de 1 local na memória para outro e veja quão rápido é.
Editar
Mudei meu código para usar as chamadas de nível inferior:
ou seja, sem buffer.
Isso não fez diferença.
NOTA : Minha unidade é uma unidade SSD. Se você tiver uma unidade normal, poderá ver uma diferença entre as duas técnicas acima. Mas, como eu esperava, o buffer e o buffer (ao gravar grandes pedaços maiores que o tamanho do buffer) não fazem diferença.
Edição 2:
Você já tentou o método mais rápido de copiar arquivos em C ++
fonte
FILE*
.A melhor solução é implementar uma gravação assíncrona com buffer duplo.
Veja a linha do tempo:
O 'F' representa o tempo de preenchimento do buffer e o 'W' representa o tempo de gravação do buffer no disco. Portanto, o problema de perder tempo entre gravar buffers no arquivo. No entanto, implementando a gravação em um thread separado, você pode começar a preencher o próximo buffer imediatamente assim:
F - preenchendo o 1º buffer
f - preenchendo o 2º buffer
W - escrevendo o 1º buffer no arquivo
w - escrevendo o 2º buffer no arquivo
_ - aguarde enquanto a operação está concluída
Essa abordagem com trocas de buffer é muito útil quando o preenchimento de um buffer requer computação mais complexa (portanto, mais tempo). Eu sempre implementei uma classe CSequentialStreamWriter que oculta a gravação assíncrona por dentro; portanto, para o usuário final, a interface possui apenas funções de gravação.
E o tamanho do buffer deve ser múltiplo do tamanho do cluster de disco. Caso contrário, você terá um desempenho ruim gravando um único buffer em 2 clusters de disco adjacentes.
Escrevendo o último buffer.
Quando você chama a função Write pela última vez, você deve garantir que o buffer atual esteja sendo preenchido também deve ser gravado no disco. Portanto, CSequentialStreamWriter deve ter um método separado, digamos Finalize (final buffer flush), que deve gravar no disco a última parte dos dados.
Manipulação de erros.
Enquanto o código começa a preencher o segundo buffer e o 1º está sendo gravado em um thread separado, mas a gravação falha por algum motivo, o thread principal deve estar ciente dessa falha.
Vamos supor que a interface de um CSequentialStreamWriter tenha a função Write retorna bool ou lança uma exceção, portanto, com um erro em um thread separado, você deve se lembrar desse estado; portanto, da próxima vez que você chamar Write ou Finilize no thread principal, o método retornará False ou lançará uma exceção. E realmente não importa em que ponto você parou de preencher um buffer, mesmo se você escrevesse alguns dados antes da falha - provavelmente o arquivo estaria corrompido e inútil.
fonte
Eu sugiro tentar o mapeamento de arquivos . Eu usei
mmap
no passado, em um ambiente UNIX, e fiquei impressionado com o alto desempenho que consegui alcançarfonte
Em
FILE*
vez disso, você poderia usar e medir o desempenho obtido? Algumas opções são para usar emfwrite/write
vez defstream
:Se você decidir usar
write
, tente algo semelhante:Eu também aconselho você a procurar
memory map
. Essa pode ser a sua resposta. Uma vez eu tive que processar um arquivo de 20 GB em outro para armazená-lo no banco de dados, e o arquivo como nem mesmo abrindo. Portanto, a solução é utilizar o mapa de memória. Eu fiz isso noPython
entanto.fonte
FILE*
equivalente direto do código original, usando o mesmo buffer de 512 MB, obtém velocidade máxima. Seu buffer atual é muito pequeno.2
corresponde a erro padrão, mas ainda é recomendável que você use emSTDERR_FILENO
vez de2
. Outra questão importante é que um possível erro que você pode obter é o EINTR; quando você recebe um sinal de interrupção, esse não é um erro real e você deve simplesmente tentar novamente.Tente usar as chamadas de API open () / write () / close () e experimente o tamanho do buffer de saída. Quero dizer, não passe o buffer inteiro de "muitos bytes" de uma só vez, faça algumas gravações (ou seja, TotalNumBytes / OutBufferSize). OutBufferSize pode ser de 4096 bytes a megabyte.
Outra tentativa - use o WinAPI OpenFile / CreateFile e use este artigo do MSDN para desativar o buffer (FILE_FLAG_NO_BUFFERING). E este artigo do MSDN sobre WriteFile () mostra como obter o tamanho do bloco para a unidade saber o tamanho ideal do buffer.
De qualquer forma, std :: ofstream é um invólucro e pode estar bloqueando as operações de E / S. Lembre-se de que percorrer toda a matriz N-gigabyte também leva algum tempo. Enquanto você escreve um pequeno buffer, ele chega ao cache e funciona mais rápido.
fonte
fstream
s não são mais lentos que os fluxos C, por si só, mas usam mais CPU (especialmente se o buffer não estiver configurado corretamente). Quando uma CPU satura, limita a taxa de E / S.Pelo menos, a implementação do MSVC 2015 copia 1 caracter por vez no buffer de saída quando um buffer de fluxo não está definido (consulte
streambuf::xsputn
). Portanto, certifique-se de definir um buffer de fluxo (> 0) .Posso obter uma velocidade de gravação de 1500MB / s (a velocidade total do meu SSD M.2)
fstream
usando este código:Eu tentei esse código em outras plataformas (Ubuntu, FreeBSD) e não notei diferenças na taxa de E / S, mas uma diferença de uso da CPU de cerca de 8: 1 (
fstream
usada 8 vezes mais CPU ). Então, pode-se imaginar, se eu tivesse um disco mais rápido, afstream
gravação diminuiria mais cedo que astdio
versão.fonte
Tente usar arquivos mapeados na memória.
fonte
ReadFile
para acessos sequenciais, embora para acessos aleatórios possam ser melhores.Se você copiar algo do disco A para o disco B no explorer, o Windows empregará DMA. Isso significa que, na maior parte do processo de cópia, a CPU basicamente não fará nada além de dizer ao controlador de disco onde colocar e obter dados, eliminando uma etapa inteira da cadeia e uma que não é otimizada para mover grandes quantidades. de dados - e eu quero dizer hardware.
O que você faz envolve muito a CPU. Quero apontar para a parte "Alguns cálculos para preencher uma []". O que eu acho que é essencial. Você gera um [], depois copia de um [] para um buffer de saída (é o que o fstream :: write faz), depois gera novamente etc.
O que fazer? Multithreading! (Espero que você tenha um processador multi-core)
fonte
Se você deseja gravar rapidamente em fluxos de arquivos, pode tornar o buffer de leitura maior:
Além disso, ao gravar muitos dados em arquivos, às vezes é mais rápido estender logicamente o tamanho do arquivo em vez de fisicamente, porque, ao estender logicamente um arquivo, o sistema de arquivos não zera o novo espaço antes de gravá-lo. Também é inteligente estender logicamente o arquivo mais do que o necessário para evitar muitas extensões de arquivo. A extensão de arquivo lógico é suportada no Windows chamando
SetFileValidData
ouxfsctl
comXFS_IOC_RESVSP64
sistemas XFS.fonte
estou compilando meu programa em gcc no GNU / Linux e mingw no win 7 e win xp e funcionou bem
você pode usar o meu programa e criar um arquivo de 80 GB basta alterar a linha 33 para
Quando sair do programa, o arquivo será destruído e, em seguida, verifique o arquivo quando estiver em execução.
ter o programa que você quer apenas mudar o programa
primeiro é o programa windows e o segundo é para GNU / Linux
http://mustafajf.persiangig.com/Projects/File/WinFile.cpp
http://mustafajf.persiangig.com/Projects/File/File.cpp
fonte