Eu procuro uma boa maneira de copiar um arquivo (binário ou texto). Eu escrevi várias amostras, todo mundo trabalha. Mas quero ouvir a opinião de programadores experientes.
Faltando bons exemplos e procurar uma maneira que funciona com C ++.
ANSI-C-WAY
#include <iostream>
#include <cstdio> // fopen, fclose, fread, fwrite, BUFSIZ
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE default is 8192 bytes
// BUFSIZE of 1 means one chareter at time
// good values should fit to blocksize, like 1024 or 4096
// higher values reduce number of system calls
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
FILE* source = fopen("from.ogv", "rb");
FILE* dest = fopen("to.ogv", "wb");
// clean and more secure
// feof(FILE* stream) returns non-zero if the end of file indicator for stream is set
while (size = fread(buf, 1, BUFSIZ, source)) {
fwrite(buf, 1, size, dest);
}
fclose(source);
fclose(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
POSIX-WAY (K&R usa isso em "A linguagem de programação C", mais baixo nível)
#include <iostream>
#include <fcntl.h> // open
#include <unistd.h> // read, write, close
#include <cstdio> // BUFSIZ
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
// BUFSIZE defaults to 8192
// BUFSIZE of 1 means one chareter at time
// good values should fit to blocksize, like 1024 or 4096
// higher values reduce number of system calls
// size_t BUFFER_SIZE = 4096;
char buf[BUFSIZ];
size_t size;
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
while ((size = read(source, buf, BUFSIZ)) > 0) {
write(dest, buf, size);
}
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
KISS-C ++ - Streambuffer-WAY
#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
dest << source.rdbuf();
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
COPY-ALGORITHM-C ++ - MANEIRA
#include <iostream>
#include <fstream>
#include <ctime>
#include <algorithm>
#include <iterator>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
istreambuf_iterator<char> begin_source(source);
istreambuf_iterator<char> end_source;
ostreambuf_iterator<char> begin_dest(dest);
copy(begin_source, end_source, begin_dest);
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
PRÓPRIO-BUFFER-C ++ - MANEIRA
#include <iostream>
#include <fstream>
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
ifstream source("from.ogv", ios::binary);
ofstream dest("to.ogv", ios::binary);
// file size
source.seekg(0, ios::end);
ifstream::pos_type size = source.tellg();
source.seekg(0);
// allocate memory for buffer
char* buffer = new char[size];
// copy file
source.read(buffer, size);
dest.write(buffer, size);
// clean up
delete[] buffer;
source.close();
dest.close();
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
LINUX-WAY // requer o kernel> = 2.6.33
#include <iostream>
#include <sys/sendfile.h> // sendfile
#include <fcntl.h> // open
#include <unistd.h> // close
#include <sys/stat.h> // fstat
#include <sys/types.h> // fstat
#include <ctime>
using namespace std;
int main() {
clock_t start, end;
start = clock();
int source = open("from.ogv", O_RDONLY, 0);
int dest = open("to.ogv", O_WRONLY | O_CREAT /*| O_TRUNC/**/, 0644);
// struct required, rationale: function stat() exists also
struct stat stat_source;
fstat(source, &stat_source);
sendfile(dest, source, 0, stat_source.st_size);
close(source);
close(dest);
end = clock();
cout << "CLOCKS_PER_SEC " << CLOCKS_PER_SEC << "\n";
cout << "CPU-TIME START " << start << "\n";
cout << "CPU-TIME END " << end << "\n";
cout << "CPU-TIME END - START " << end - start << "\n";
cout << "TIME(SEC) " << static_cast<double>(end - start) / CLOCKS_PER_SEC << "\n";
return 0;
}
Meio Ambiente
- GNU / LINUX (Archlinux)
- Kernel 3.3
- GLIBC-2.15, LIBSTDC ++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
- Usando o RUNLEVEL 3 (multiusuário, rede, terminal, sem GUI)
- SSD INTEL-Postville 80 GB, preenchido até 50%
- Copie um OGG-VIDEO-FILE de 270 MB
Passos para reproduzir
1. $ rm from.ogg
2. $ reboot # kernel and filesystem buffers are in regular
3. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file
4. $ sha256sum *.ogv # checksum
5. $ rm to.ogg # remove copy, but no sync, kernel and fileystem buffers are used
6. $ (time ./program) &>> report.txt # executes program, redirects output of program and append to file
Resultados (tempo de CPU usado)
Program Description UNBUFFERED|BUFFERED
ANSI C (fread/frwite) 490,000|260,000
POSIX (K&R, read/write) 450,000|230,000
FSTREAM (KISS, Streambuffer) 500,000|270,000
FSTREAM (Algorithm, copy) 500,000|270,000
FSTREAM (OWN-BUFFER) 500,000|340,000
SENDFILE (native LINUX, sendfile) 410,000|200,000
O tamanho do arquivo não muda.
sha256sum imprime os mesmos resultados.
O arquivo de vídeo ainda pode ser reproduzido.
Questões
- Qual método você prefere?
- Você conhece melhores soluções?
- Você vê algum erro no meu código?
Você conhece um motivo para evitar uma solução?
FSTREAM (KISS, Streambuffer)
Eu realmente gosto deste, porque é muito curto e simples. Até onde eu sei, o operador << está sobrecarregado para rdbuf () e não converte nada. Corrigir?
obrigado
Atualização 1
Alterei a fonte em todas as amostras dessa maneira, para que a abertura e o fechamento dos descritores de arquivo sejam incluídos na medição do relógio () . Não existem outras alterações significativas no código fonte. Os resultados não mudaram! Também usei o tempo para verificar meus resultados.
A
amostra da atualização 2 ANSI C foi alterada: a condição do loop while não chama mais feof (), em vez disso, mudei fread () para a condição. Parece que o código roda agora 10.000 relógios mais rápidos.
A medição mudou: os resultados anteriores sempre foram armazenados em buffer, porque eu repeti a linha de comando antiga rm em .ogv && sync && time ./program para cada programa algumas vezes. Agora eu reinicio o sistema para todos os programas. Os resultados inalterados são novos e não mostram nenhuma surpresa. Os resultados sem buffer não mudaram realmente.
Se eu não excluir a cópia antiga, os programas reagem de maneira diferente. A substituição de um arquivo existente em buffer é mais rápida com POSIX e SENDFILE; todos os outros programas são mais lentos. Talvez as opções truncem ou criem tenham um impacto nesse comportamento. Mas substituir arquivos existentes pela mesma cópia não é um caso de uso do mundo real.
A execução da cópia com cp leva 0,44 segundos sem buffer e 0,30 segundos com buffer. Portanto, cp é um pouco mais lento que a amostra POSIX. Parece bom para mim.
Talvez eu adicione também exemplos e resultados de mmap () e copy_file()
de boost :: filesystem.
Atualização 3
Coloquei isso também em uma página de blog e estendi um pouco. Incluindo splice () , que é uma função de baixo nível do kernel do Linux. Talvez mais amostras com Java sigam.
http://www.ttyhoney.com/blog/?page_id=69
fstream
definitivamente é uma boa opção para operações de arquivo.#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
Respostas:
Copie um arquivo de maneira sã:
É tão simples e intuitivo de ler que vale a pena o custo extra. Se estivéssemos fazendo isso muito, é melhor recorrer às chamadas do SO para o sistema de arquivos. Tenho certeza que
boost
tem um método de arquivo de cópia em sua classe de sistema de arquivos.Existe um método C para interagir com o sistema de arquivos:
fonte
copyfile
não é portátil; Eu acho que é específico para o Mac OS X. Certamente não existe no Linux.boost::filesystem::copy_file
é provavelmente a maneira mais portátil de copiar um arquivo através do sistema de arquivos nativo.Com o C ++ 17, a maneira padrão de copiar um arquivo incluirá o
<filesystem>
cabeçalho e usará :O primeiro formulário é equivalente ao segundo,
copy_options::none
usado como opções (veja tambémcopy_file
).A
filesystem
biblioteca foi originalmente desenvolvidaboost.filesystem
e, finalmente, mesclada ao ISO C ++ a partir do C ++ 17.fonte
bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);
?Muitos!
O buffer de caminho "ANSI C" é redundante, pois a
FILE
já está em buffer. (O tamanho desse buffer interno é o queBUFSIZ
realmente define.)O "OWN-BUFFER-C ++ - WAY" ficará lento à medida que passar
fstream
, o que faz muito envio virtual e mantém novamente buffers internos ou cada objeto de fluxo. (O "COPY-ALGORITHM-C ++ - WAY" não sofre isso, pois ostreambuf_iterator
classe ignora a camada de fluxo.)Eu prefiro o "COPY-ALGORITHM-C ++ - WAY", mas sem construir um
fstream
, basta criarstd::filebuf
instâncias nuas quando nenhuma formatação real for necessária.Para desempenho bruto, você não pode superar os descritores de arquivo POSIX. É feio, mas portátil e rápido em qualquer plataforma.
A maneira do Linux parece incrivelmente rápida - talvez o sistema operacional deixe a função retornar antes que a E / S seja concluída? De qualquer forma, isso não é portátil o suficiente para muitos aplicativos.
EDIT : Ah, "Linux nativo" pode estar melhorando o desempenho, intercalando leituras e gravações com E / S assíncronas. Deixar os comandos se acumularem pode ajudar o driver de disco a decidir quando é melhor procurar. Você pode tentar o Boost Asio ou pthreads para comparação. Quanto a "não pode superar os descritores de arquivos POSIX" ... bem, isso é verdade se você estiver fazendo alguma coisa com os dados, não apenas copiando cegamente.
fonte
sendfile()
copia dados entre um descritor de arquivo e outro. Como essa cópia é feita no kernel,sendfile()
é mais eficiente que a combinação deread(2)
ewrite(2)
, o que exigiria a transferência de dados para e do espaço do usuário.": kernel.org/doc/man-pages /online/pages/man2/sendfile.2.htmlfilebuf
objetos brutos ?Eu quero fazer o muito observação importante que o método utilizando LINUX sendfile () tem um grande problema na medida em que não é possível copiar arquivos de mais de 2GB de tamanho! Eu o havia implementado após esta pergunta e estava tendo problemas porque estava usando-o para copiar arquivos HDF5 com muitos GB de tamanho.
http://man7.org/linux/man-pages/man2/sendfile.2.html
fonte
off64_t
permite usar um loop para copiar arquivos grandes, como mostrado em uma resposta à pergunta vinculada.O Qt possui um método para copiar arquivos:
Observe que, para usar isso, você deve instalar o Qt (instruções aqui ) e incluí-lo no seu projeto (se você estiver usando o Windows e não for um administrador, poderá fazer o download do Qt aqui ). Veja também esta resposta .
fonte
QFile::copy
é ridiculamente lento devido ao seu buffer de 4k .Qt
. Estou usando5.9.2
e a velocidade está a par da implementação nativa. Btw. dando uma olhada no código fonte, o Qt parece realmente chamar a implementação nativa.Para quem gosta de impulso:
Observe que o boost :: filesystem :: path também está disponível como wpath para Unicode. E que você também pode usar
se você não gostar desses nomes longos
fonte
Não sei bem o que é uma "boa maneira" de copiar um arquivo, mas, assumindo que "bom" significa "rápido", poderia ampliar um pouco o assunto.
Os sistemas operacionais atuais foram otimizados há muito tempo para lidar com a cópia de arquivo da fábrica. Nenhum pedaço inteligente de código superará isso. É possível que algumas variantes de suas técnicas de cópia sejam mais rápidas em alguns cenários de teste, mas provavelmente se sairiam pior em outros casos.
Normalmente, o
sendfile
função provavelmente retorna antes que a gravação seja confirmada, dando a impressão de ser mais rápida que o resto. Eu não li o código, mas é certamente porque ele aloca seu próprio buffer dedicado, trocando memória por tempo. E a razão pela qual não funcionará para arquivos maiores que 2 GB.Enquanto você lida com um pequeno número de arquivos, tudo ocorre dentro de vários buffers (o primeiro tempo de execução do C ++, se você usar
iostream
, os internos do SO, aparentemente um buffer extra do tamanho de um arquivosendfile
). A mídia de armazenamento real só é acessada quando dados suficientes foram movidos para valer a pena o trabalho de girar um disco rígido.Suponho que você possa melhorar um pouco o desempenho em casos específicos. Em cima da minha cabeça:
copy_file
sequencialmente (embora você dificilmente notará a diferença enquanto o arquivo couber no cache do SO)Mas tudo isso está fora do escopo de uma função de cópia de arquivo de uso geral.
Portanto, na minha opinião de programador indiscutivelmente experiente, uma cópia de arquivo C ++ deve apenas usar a
file_copy
função dedicada C ++ 17 , a menos que se saiba mais sobre o contexto em que a cópia do arquivo ocorre e que algumas estratégias inteligentes possam ser criadas para superar o SO.fonte