c++ pasos - Copia un archivo de forma sana, segura y eficiente.




mover directorios (5)

¡Demasiados!

El búfer de manera "ANSI C" es redundante, ya que un FILE ya está en búfer. (El tamaño de este búfer interno es lo que realmente define BUFSIZ ).

El "OWN-BUFFER-C ++ - WAY" será lento a medida que avanza a través de fstream , que realiza una gran cantidad de envíos virtuales y, de nuevo, mantiene los búferes internos o cada objeto de flujo. (La "COPY-ALGORITHM-C ++ - WAY" no sufre esto, ya que la clase streambuf_iterator pasa por alto la capa de la secuencia.)

Prefiero el "COPY-ALGORITHM-C ++ - WAY", pero sin construir un fstream , simplemente cree las instancias std::filebuf cuando no se necesita un formato real.

Para un rendimiento en bruto, no se pueden superar los descriptores de archivos POSIX. Es feo pero portátil y rápido en cualquier plataforma.

La forma de Linux parece ser increíblemente rápida: ¿tal vez el sistema operativo permita que la función vuelva antes de que se complete la E / S? En cualquier caso, eso no es lo suficientemente portátil para muchas aplicaciones.

EDIT : Ah, "Linux nativo" puede estar mejorando el rendimiento intercalando lecturas y escrituras con E / S asíncronas. Permitir que los comandos se acumulen puede ayudar al controlador del disco a decidir cuándo es mejor buscar. Puedes probar Boost Asio o pthreads para comparar. En cuanto a "no se pueden superar los descriptores de archivos POSIX" ... bueno, eso es cierto si está haciendo algo con los datos, no solo copiando a ciegas.

Busco una buena manera de copiar un archivo (binario o texto). He escrito varias muestras, todo el mundo trabaja. 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;
}

COPY-ALGORITHM-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;
}

OWN-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
  • Uso de RUNLEVEL 3 (multiusuario, red, terminal, sin GUI)
  • INTEL SSD-Postville 80 GB, llenado hasta un 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 (CPU TIME utilizado)

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 imprimir los mismos resultados.
El archivo de video todavía 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, para que la apertura y el cierre de los descriptores de archivo se incluyan 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.

Actualización 2
La muestra de ANSI C cambió: la condición del bucle while no llama a más feof (), en cambio moví fread () a la condición. Parece que, el código corre ahora 10,000 relojes más rápido.

Medida cambiada: los resultados anteriores siempre estaban almacenados en búfer, porque repetí la antigua línea de comando rm para.ogv && sync && time ./program para cada programa unas cuantas veces. Ahora reinicio el sistema para cada programa. Los resultados no amortiguados son nuevos y no muestran sorpresas. Los resultados no amortiguados no cambiaron realmente.

Si no borro la copia antigua, los programas reaccionan de manera diferente. Sobrescribir un archivo existente almacenado en búfer es más rápido con POSIX y SENDFILE, todos los demás programas son más lentos. Tal vez las opciones truncadas o creadas tengan un impacto en este comportamiento. Pero sobrescribir los archivos existentes con la misma copia no es un caso de uso del mundo real.

Realizar la copia con cp toma 0.44 segundos sin búfer y 0.30 segundos en búfer. Así que cp es un poco más lento que el de POSIX. Se ve bien para mi

Quizás añada también muestras y resultados de mmap () y copy_file() de boost :: filesystem.

Actualización 3
He puesto esto también 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. Tal vez más muestras con Java seguirán. http://www.ttyhoney.com/blog/?page_id=69


Copia un archivo de una manera sana:

#include <fstream>

int main()
{
    std::ifstream  src("from.ogv", std::ios::binary);
    std::ofstream  dst("to.ogv",   std::ios::binary);

    dst << src.rdbuf();
}

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 archivo en su clase de sistema de archivos.

Existe un método en C para interactuar con el sistema de archivos:

#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);

Con C ++ 17, la forma estándar de copiar un archivo será incluir el encabezado <filesystem> y usar:

bool copy_file( const std::filesystem::path& from,
                const std::filesystem::path& to);

bool copy_file( const std::filesystem::path& from,
                const std::filesystem::path& to,
                std::filesystem::copy_options options);

La primera forma es equivalente a la segunda con copy_options::none usado como opciones (vea también copy_file ).

La biblioteca del filesystem se desarrolló originalmente como boost.filesystem y finalmente se fusionó con ISO C ++ a partir de C ++ 17.


Qt tiene un método para copiar archivos:

#include <QFile>
QFile::copy("originalFile.example","copiedFile.example");

Tenga en cuenta que para usar esto debe instalar Qt (instrucciones here ) e incluirlo en su proyecto (si está usando Windows y no es un administrador, puede descargar Qt here ). También vea esta respuesta .


Una opción extremadamente liviana puede ser usar tablas HTML. Simplemente cree etiquetas de cabeza, cuerpo y tabla en un archivo, y guárdelo como un archivo con la extensión .xls. Hay atributos específicos de Microsoft que puede usar para dar estilo a la salida, incluidas las fórmulas.

Me doy cuenta de que puede no estar codificando esto en una aplicación web, pero aquí hay un example de la composición de un archivo de Excel a través de una tabla HTML. Esta técnica podría utilizarse si estaba codificando una aplicación de consola, una aplicación de escritorio o un servicio.





c++ file-io