Busco una buena manera de copiar un archivo (binario o texto). He escrito varias muestras, todos trabajan. Pero quiero escuchar la opinión de programadores experimentados.
Me faltan buenos ejemplos y busco una forma que funcione con 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 esto en "El lenguaje de programación C", más bajo nivel)
#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;
}
COPIA-ALGORITMO-C ++ - CAMINO
#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;
}
PROPIO-BUFFER-C ++ - CAMINO
#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 // requiere 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;
}
Ambiente
- GNU / LINUX (Archlinux)
- Kernel 3.3
- GLIBC-2.15, LIBSTDC ++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
- Usando RUNLEVEL 3 (Multiusuario, Red, Terminal, sin GUI)
- INTEL SSD-Postville 80 GB, lleno hasta 50%
- Copie un archivo de video de 270 MB OGG
pasos para reproducir
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 (TIEMPO 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
El tamaño del archivo no cambia.
sha256sum imprime los mismos resultados.
El archivo de video aún se puede reproducir.
Preguntas
- ¿Qué método preferirías?
- ¿Conoces mejores soluciones?
- ¿Ves algún error en mi código?
¿Conoces una razón para evitar una solución?
FSTREAM (KISS, Streambuffer)
Realmente me gusta este, porque es muy corto y simple. Hasta donde sé, el operador << está sobrecargado para rdbuf () y no convierte nada. ¿Correcto?
Gracias
Actualización 1
Cambié la fuente en todas las muestras de esa manera, que la apertura y el cierre de los descriptores de archivo se incluyen en la medición del reloj () . No hay otros cambios significativos en el código fuente. ¡Los resultados no han cambiado! También usé el tiempo para verificar mis resultados.
La actualización de la
muestra 2 ANSI C cambió: la condición del bucle while ya no llama a feof () sino que moví fread () a la condición. Parece que el código se ejecuta ahora 10.000 relojes más rápido.
La medición cambió: los resultados anteriores siempre se almacenaron temporalmente , porque repetí la antigua línea de comando rm to.ogv && sync && time ./program para cada programa varias veces. Ahora reinicio el sistema para cada programa. Los resultados no almacenados son nuevos y no muestran sorpresa. Los resultados sin búfer no cambiaron realmente.
Si no elimino la copia anterior, los programas reaccionan de manera diferente. Sobrescribir un archivo existente almacenado en el búfer es más rápido con POSIX y SENDFILE, todos los demás programas son más lentos. Quizás las opciones truncadas o creadas tengan un impacto en este comportamiento. Pero sobrescribir archivos existentes con la misma copia no es un caso de uso del mundo real.
Realizar la copia con cp tarda 0,44 segundos sin búfer y 0,30 segundos con búfer. Entonces cp es un poco más lento que el ejemplo POSIX. Se ve bien para mi.
Quizás agregue también muestras y resultados de mmap () y copy_file()
de boost :: filesystem.
Actualización 3
También puse esto en una página de blog y lo extendí un poco. Incluyendo splice () , que es una función de bajo nivel del kernel de Linux. Quizás sigan más muestras con Java.
http://www.ttyhoney.com/blog/?page_id=69
fstream
Definitivamente es una buena opción para las operaciones de archivo.#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
Respuestas:
Copie un archivo de una manera sensata:
Esto es tan simple e intuitivo de leer que vale la pena el costo adicional. Si lo estuviéramos haciendo mucho, mejor recurrir a las llamadas del sistema operativo al sistema de archivos. Estoy seguro de que
boost
tiene un método de copia de archivos en su clase de sistema de archivos.Hay un método C para interactuar con el sistema de archivos:
fuente
copyfile
no es portátil; Creo que es específico para Mac OS X. Ciertamente no existe en Linux.boost::filesystem::copy_file
es probablemente la forma más portátil de copiar un archivo a través del sistema de archivos nativo.Con C ++ 17, la forma estándar de copiar un archivo será incluir el
<filesystem>
encabezado y usar:La primera forma es equivalente a la segunda con las
copy_options::none
opciones utilizadas (ver tambiéncopy_file
).La
filesystem
biblioteca se desarrolló originalmenteboost.filesystem
y finalmente se fusionó con ISO C ++ a partir de C ++ 17.fuente
bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);
?¡Demasiados!
El búfer de modo "ANSI C" es redundante, ya que a
FILE
ya está almacenado. (El tamaño de este búfer interno es lo queBUFSIZ
realmente define).El "OWN-BUFFER-C ++ - WAY" será lento a medida que avanza
fstream
, lo que hace una gran cantidad de despacho virtual, y nuevamente mantiene buffers internos o cada objeto de flujo. (El "COPY-ALGORITHM-C ++ - WAY" no sufre esto, ya que lastreambuf_iterator
clase evita la capa de flujo).Prefiero el "COPY-ALGORITHM-C ++ - WAY", pero sin construir un
fstream
, simplemente creestd::filebuf
instancias desnudas cuando no se necesite un formato real.Para un rendimiento sin procesar, no puede superar los descriptores de archivo POSIX. Es feo pero portátil y rápido en cualquier plataforma.
La forma de Linux parece ser increíblemente rápida: ¿quizás el sistema operativo dejó que la función volviera antes de que finalizara la E / S? En cualquier caso, eso no es lo suficientemente portátil para muchas aplicaciones.
EDITAR : Ah, "Linux nativo" puede estar mejorando el rendimiento al intercalar lecturas y escrituras con E / S asíncrona. Dejar que los comandos se acumulen puede ayudar al controlador de disco a decidir cuándo es mejor buscar. Puede probar Boost Asio o pthreads para comparar. En cuanto a "no se puede superar a los descriptores de archivos POSIX" ... bueno, eso es cierto si está haciendo algo con los datos, no solo copiando a ciegas.
fuente
sendfile()
copia datos entre un descriptor de archivo y otro. Debido a que esta copia se realiza dentro del núcleo,sendfile()
es más eficiente que la combinación deread(2)
ywrite(2)
, lo que requeriría transferir datos hacia y desde el espacio del usuario".: kernel.org/doc/man-pages /online/pages/man2/sendfile.2.htmlfilebuf
objetos sin procesar ?Quiero hacer una nota muy importante de que el método LINUX que usa sendfile () tiene un gran problema, ya que no puede copiar archivos de más de 2 GB de tamaño. Lo implementé después de esta pregunta y tuve problemas porque lo estaba usando para copiar archivos HDF5 que tenían muchos GB.
http://man7.org/linux/man-pages/man2/sendfile.2.html
fuente
off64_t
permite utilizar un bucle para copiar archivos grandes como se muestra en una respuesta a la pregunta vinculada.Qt tiene un método para copiar archivos:
Tenga en cuenta que para usar esto debe instalar Qt (instrucciones aquí ) e incluirlo en su proyecto (si está utilizando Windows y no es un administrador, puede descargar Qt aquí ). También vea esta respuesta .
fuente
QFile::copy
es ridículamente lento debido a su almacenamiento en búfer de 4k .Qt
. Estoy usando5.9.2
y la velocidad está a la par con la implementación nativa. Por cierto. echando un vistazo al código fuente, Qt parece llamar a la implementación nativa.Para aquellos que les gusta el impulso:
Tenga en cuenta que boost :: filesystem :: path también está disponible como wpath para Unicode. Y que también podrías usar
si no te gustan esos nombres largos
fuente
No estoy muy seguro de qué es una "buena forma" de copiar un archivo, pero suponiendo que "buena" significa "rápido", podría ampliar un poco el tema.
Los sistemas operativos actuales se han optimizado durante mucho tiempo para manejar la ejecución de la copia del archivo mill. Ningún código inteligente superará eso. Es posible que alguna variante de sus técnicas de copia resulte más rápida en algún escenario de prueba, pero lo más probable es que les vaya peor en otros casos.
Por lo general, la
sendfile
función probablemente regresa antes de que se haya confirmado la escritura, dando la impresión de ser más rápida que el resto. No he leído el código, pero ciertamente es porque asigna su propio búfer dedicado, intercambiando memoria por tiempo. Y la razón por la que no funcionará para archivos de más de 2 Gb.Mientras se trata de una pequeña cantidad de archivos, todo ocurre dentro de varios búferes (el tiempo de ejecución de C ++ es el primero si lo usa
iostream
, los internos del sistema operativo, aparentemente un búfer adicional del tamaño de un archivo en el caso desendfile
). Solo se accede al medio de almacenamiento real una vez que se han movido suficientes datos para que valga la pena girar un disco duro.Supongo que podría mejorar ligeramente las actuaciones en casos específicos. La parte superior de mi cabeza:
copy_file
secuencialmente (aunque apenas notará la diferencia siempre que el archivo se ajuste en la memoria caché del sistema operativo)Pero todo eso está fuera del alcance de una función de copia de archivos de propósito general.
Entonces, en la opinión de mi programador posiblemente experimentado, una copia de archivo C ++ solo debe usar la
file_copy
función dedicada C ++ 17 , a menos que se sepa más sobre el contexto donde se produce la copia del archivo y se pueden diseñar algunas estrategias inteligentes para burlar al sistema operativo.fuente