351 Stimmen

Kopieren einer Datei auf eine vernünftige, sichere und effiziente Weise

Ich suche nach einer guten Möglichkeit, eine Datei (binär oder Text) zu kopieren. Ich habe mehrere Beispiele geschrieben, alle funktionieren. Aber ich möchte die Meinung von erfahrenen Programmierern hören.

Ich vermisse gute Beispiele und suche einen Weg, der mit C++ funktioniert.

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 verwenden dies in "Die Programmiersprache C", mehr auf niedriger Ebene)

#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;
}

KOPIER-ALGORITHMUS-C++-WEISE

#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;
}

EIGENER-PUFFER-C++-WEG

#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 // erfordert 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;
}

Umwelt

  • GNU/LINUX (Archlinux)
  • Kernel 3.3
  • GLIBC-2.15, LIBSTDC++ 4.7 (GCC-LIBS), GCC 4.7, Coreutils 8.16
  • Verwendung von RUNLEVEL 3 (Mehrbenutzer, Netzwerk, Terminal, keine GUI)
  • INTEL SSD-Postville 80 GB, bis zu 50% gefüllt
  • Kopieren einer 270 MB OGG-VIDEO-DATEI

Schritte zur Reproduktion

 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

Ergebnisse (verbrauchte CPU-Zeit)

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  

Die Dateigröße ändert sich nicht.
sha256sum liefern die gleichen Ergebnisse.
Die Videodatei ist noch abspielbar.

Fragen

  • Welche Methode würden Sie bevorzugen?

  • Kennen Sie bessere Lösungen?

  • Siehst du irgendwelche Fehler in meinem Code?

  • Kennen Sie einen Grund, um eine Lösung zu vermeiden?

  • FSTREAM (KISS, Streambuffer)
    Das gefällt mir sehr gut, weil es wirklich kurz und einfach ist. Soweit ich weiß, ist der Operator << für rdbuf() überladen und konvertiert nichts. Korrekt?

Danke

Aktualisierung 1
Ich habe den Quelltext in allen Beispielen so geändert, dass das Öffnen und Schließen der Dateideskriptoren in die Messung von Uhr() . Es gibt keine weiteren signifikanten Änderungen im Quellcode. Die Ergebnisse haben sich nicht verändert! Ich habe auch Zeit um meine Ergebnisse zu überprüfen.

Aktualisierung 2
ANSI C-Beispiel geändert: Der Zustand der while-Schleife ruft nicht mehr an feof() stattdessen bin ich umgezogen fread() in den Zustand. Es sieht so aus, als ob der Code jetzt 10.000 Takte schneller läuft.

Messung geändert: Die bisherigen Ergebnisse wurden immer gepuffert, weil ich die alte Befehlszeile wiederholt habe rm to.ogv && sync && time ./program für jedes Programm ein paar Mal. Jetzt starte ich das System für jedes Programm neu. Die ungepufferten Ergebnisse sind neu und zeigen keine Überraschungen. Die ungepufferten Ergebnisse haben sich nicht wirklich verändert.

Wenn ich die alte Kopie nicht lösche, reagieren die Programme anders. Überschreiben einer bestehenden Datei gepuffert ist mit POSIX und SENDFILE schneller, alle anderen Programme sind langsamer. Vielleicht sind die Optionen abschneiden. o erstellen. Auswirkungen auf dieses Verhalten haben. Das Überschreiben vorhandener Dateien mit derselben Kopie ist jedoch kein realer Anwendungsfall.

Ausführen der Kopie mit cp benötigt 0,44 Sekunden ungepuffert und 0,30 Sekunden gepuffert. Also cp ist ein wenig langsamer als das POSIX-Beispiel. Für mich sieht es gut aus.

Vielleicht füge ich auch Beispiele und Ergebnisse von mmap() y _copy_file()_ von boost::filesystem.

Aktualisierung 3
Ich habe das auch auf eine Blogseite gestellt und ein wenig erweitert. Inklusive splice() die eine Low-Level-Funktion des Linux-Kernels ist. Vielleicht werden weitere Beispiele mit Java folgen. http://www.ttyhoney.com/blog/?page_id=69

3 Stimmen

#include <copyfile.h> copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);

2voto

anhoppe Punkte 3769

Für diejenigen, die Boost mögen:

boost::filesystem::path mySourcePath("foo.bar");
boost::filesystem::path myTargetPath("bar.foo");

// Variant 1: Overwrite existing
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::overwrite_if_exists);

// Variant 2: Fail if exists
boost::filesystem::copy_file(mySourcePath, myTargetPath, boost::filesystem::copy_option::fail_if_exists);

Beachten Sie, dass boost::filesystem::path ist auch erhältlich als wpath für Unicode. Und dass Sie auch Folgendes verwenden können

using namespace boost::filesystem

wenn Sie diese langen Typenbezeichnungen nicht mögen

1voto

kuroi neko Punkte 8301

Ich bin mir nicht ganz sicher, was eine "gute Methode" zum Kopieren einer Datei ist, aber wenn man davon ausgeht, dass "gut" "schnell" bedeutet, könnte ich das Thema ein wenig ausweiten.

Aktuelle Betriebssysteme sind seit langem für den Umgang mit gewöhnlichen Dateikopien optimiert. Kein noch so cleverer Code kann das übertreffen. Es ist möglich, dass einige Varianten Ihrer Kopiertechniken sich in einigen Testszenarien als schneller erweisen, aber in anderen Fällen würden sie höchstwahrscheinlich schlechter abschneiden.

Typischerweise wird die sendfile Funktion kehrt wahrscheinlich zurück, bevor der Schreibvorgang abgeschlossen ist, und erweckt so den Eindruck, schneller als der Rest zu sein. Ich habe den Code nicht gelesen, aber das liegt mit Sicherheit daran, dass die Funktion einen eigenen Puffer zuweist und Speicher gegen Zeit tauscht. Und der Grund, warum es nicht für Dateien größer als 2Gb funktionieren wird.

Solange Sie mit einer kleinen Anzahl von Dateien zu tun haben, findet alles in verschiedenen Puffern statt (der erste der C++-Laufzeitumgebung, wenn Sie iostream die OS-internen, anscheinend ein zusätzlicher Puffer in Dateigröße im Fall von sendfile ). Auf die eigentlichen Speichermedien wird erst dann zugegriffen, wenn genügend Daten verschoben wurden, so dass sich der Aufwand für das Drehen einer Festplatte lohnt.

Ich nehme an, dass Sie die Leistungen in bestimmten Fällen leicht verbessern können. Ganz spontan fällt mir ein:

  • Wenn Sie eine große Datei auf dieselbe Festplatte kopieren, könnte die Verwendung eines Puffers, der größer ist als der des Betriebssystems, die Situation etwas verbessern (aber wir sprechen hier wahrscheinlich über Gigabytes).
  • Wenn Sie dieselbe Datei auf zwei verschiedene physische Ziele kopieren wollen, werden Sie wahrscheinlich schneller sein, wenn Sie alle drei Dateien auf einmal öffnen, als wenn Sie zwei copy_file sequentiell (obwohl Sie den Unterschied kaum bemerken werden, solange die Datei in den Cache des Betriebssystems passt)
  • Wenn Sie es mit vielen kleinen Dateien auf einer Festplatte zu tun haben, sollten Sie diese in Stapeln lesen, um die Suchzeit zu minimieren (obwohl das Betriebssystem bereits Verzeichniseinträge in den Cache stellt, um ein verrücktes Suchen zu vermeiden, und kleine Dateien die Festplattenbandbreite wahrscheinlich ohnehin drastisch reduzieren).

Aber all das liegt außerhalb des Rahmens einer allgemeinen Dateikopierfunktion.

Nach meiner Meinung als erfahrener Programmierer sollte eine C++-Dateikopie also einfach die C++17 file_copy Es sei denn, es ist mehr über den Kontext bekannt, in dem die Dateikopie stattfindet, und es können einige clevere Strategien entwickelt werden, um das Betriebssystem zu überlisten.

1voto

eliasetm Punkte 1101

Der einfachste Weg in C++17 und später ist:

Verwenden Sie die #include <filesystem> y copy() Methode. Es gibt 4 Überladungen für die Kopiermethode. Sie können das in dieser Datei überprüfen Link

void copy( const std::filesystem::path& from,

           const std::filesystem::path& to );
void copy( const std::filesystem::path& from,
           const std::filesystem::path& to,
           std::error_code& ec );

void copy( const std::filesystem::path& from,

           const std::filesystem::path& to,
           std::filesystem::copy_options options );

void copy( const std::filesystem::path& from,
           const std::filesystem::path& to,
           std::filesystem::copy_options options,
           std::error_code& ec );

Mit copy() Methode kann Dateien und Verzeichnisse mit einigen Optionen kopieren, wie z.B. rekursiv, nicht rekursiv, nur Verzeichnisse kopieren oder vorhandene Dateien überschreiben oder überspringen, usw. Sie können mehr über Kopieroptionen in diesem Link

Dies ist ein Beispielcode aus これ mit einigen Änderungen:

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <filesystem>
namespace fs = std::filesystem;

int main()
{
    // create directories. create all directories if not exist. 
    fs::create_directories("sandbox/dir/subdir");

    // create file with content 'a'
    std::ofstream("sandbox/file1.txt").put('a');

    // copy file
    fs::copy("sandbox/file1.txt", "sandbox/file2.txt");

    // copy directory (non-recursive)
    fs::copy("sandbox/dir", "sandbox/dir2"); 

    // copy directory (recursive)
    const auto copyOptions = fs::copy_options::update_existing
                           | fs::copy_options::recursive
                           ;
    fs::copy("sandbox", "sandbox_copy", copyOptions); 

    // remove sanbox directory and all sub directories and sub files.
    fs::remove_all("sandbox");
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X