Skopiuj plik w rozsądny, bezpieczny i skuteczny sposób

Szukam dobrego sposobu na skopiowanie pliku (binarnego lub tekstowego). Napisałem kilka próbek, każdy działa. Ale chcę usłyszeć opinię doświadczonych programistów.

Brakuje mi dobrych przykładów i szukam sposobu, który działa z 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 używa tego w "języku programowania C", więcej low-level)

#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++ - WAY

#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++ - 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);

    // 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 / / wymaga jądra >= 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;
}

Środowisko

  • GNU/LINUX (Archlinux)
  • Kernel 3.3
  • GLIBC-2.15, LIBSTDC++ 4.7( GCC-LIBS), GCC 4.7, Coreutils 8.16
  • używanie RUNLEVEL 3 (Multiuser, Network, Terminal, no GUI)
  • INTEL SSD-80 GB, wypełniony do 50%
  • skopiuj 270 MB OGG-VIDEO-FILE

Kroki do odtworzenia

 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

Results (cpu TIME used)

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  

Rozmiar Pliku się nie zmienia.
sha256sum drukuje te same wyniki.
Plik wideo jest nadal odtwarzany.

Pytania

  • jaką metodę wolisz?
  • czy znasz lepsze rozwiązania?
  • czy widzisz jakieś błędy w moim kodzie?
  • Czy znasz powód, aby uniknąć rozwiązania?

  • FSTREAM (KISS, Streambuffer)
    Ten bardzo mi się podoba, bo jest naprawdę krótki i prosty. Z tego co wiem operator

Dzięki

Update 1
Zmieniłem źródło we wszystkich próbkach w ten sposób, że otwieranie i zamykanie deskryptorów plików jest uwzględniane w pomiarze clock () . Ich nie są inne znaczące zmiany w kodzie źródłowym. Wyniki się nie zmieniają! Użyłem również czasu , aby dwukrotnie sprawdzić moje wyniki.

Update 2
Próbka ANSI C została zmieniona: warunek while-loop nie wywołuje już feof() zamiast tego przeniosłem fread() do warunku. Wygląda na to, że kod działa 10,000 zegarów szybciej.

Zmiana pomiaru: poprzednie wyniki były zawsze buforowane, ponieważ powtórzyłem starą linię poleceń rm do.ogv & & synchronizacja & & czas ./ program dla każdego programu kilka razy. Teraz restartuję system dla każdego programu. Nie buforowane wyniki są nowe i nie zaskakują. Wyniki nie uległy zmianie.

Jeśli nie usunę starej kopii, programy reagują inaczej. Nadpisanie istniejącego pliku buforowanego jest szybsze dzięki POSIX i SENDFILE, wszystkie inne programy są wolniejsze. Może opcje truncate lub stworzyć mieć wpływ na to zachowanie. Ale nadpisywanie istniejących plików tą samą kopią nie jest prawdziwym przypadkiem użycia.

Wykonanie kopii z cp zajmuje 0,44 sekundy niebuforowanej i 0,30 sekundy buforowanej. Więc cp jest trochę wolniejszy niż próbka POSIX. Dla mnie wygląda dobrze.

Może dodam jeszcze próbki i wyniki mmap () I copy_file() z boost:: filesystem.

Update 3
Włożyłem to również na stronie bloga i rozszerzył go trochę. W tym splice () , która jest funkcją niskiego poziomu z jądra Linuksa. Może pojawi się więcej próbek z Javą. http://www.ttyhoney.com/blog/?page_id=69

 316
Author: Peter, 2012-04-17

7 answers

Skopiuj plik w rozsądny sposób:

#include <fstream>

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

    dst << src.rdbuf();
}
Jest to tak proste i intuicyjne w czytaniu, że warte jest dodatkowych kosztów. Jeśli robiliśmy to dużo, lepiej wycofać się na wywołania systemu operacyjnego do systemu plików. Jestem pewien, że boost ma metodę copy file w swojej klasie systemu plików.

Istnieje metoda C do interakcji z systemem plików:

#include <copyfile.h>

int
copyfile(const char *from, const char *to, copyfile_state_t state, copyfile_flags_t flags);
 269
Author: Martin York,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-10-19 13:34:22

W C++17 standardowym sposobem kopiowania pliku będzie <filesystem> nagłówek i użycie:

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);

Pierwsza forma jest równoważna drugiej z copy_options::none używaną jako opcje (Zobacz też copy_file).

Biblioteka filesystem została pierwotnie opracowana jako boost.filesystem i ostatecznie scalona z ISO C++ od C++17.

 67
Author: manlio,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-27 16:15:30

Za dużo!

Bufor drogi "ANSI C" jest nadmiarowy, ponieważ FILE jest już buforowany. (Rozmiar tego wewnętrznego bufora jest tym, co Definiuje BUFSIZ.)

"OWN-BUFFER-C++-WAY" będzie wolny w miarę przechodzenia przez fstream, który wykonuje wiele wirtualnych dyspozytorów i ponownie utrzymuje wewnętrzne bufory lub każdy obiekt strumienia. ("COPY-ALGORITHM-C++-WAY" nie cierpi tego, ponieważ klasa streambuf_iterator omija warstwę stream.)

Preferuję "COPY-ALGORITHM-C++-WAY", ale bez konstruowania fstream, po prostu utwórz puste instancje std::filebuf, gdy nie jest potrzebne faktyczne formatowanie.

Dla wydajności raw, nie można pokonać deskryptorów plików POSIX. Jest brzydki, ale przenośny i szybki na każdej platformie.

Sposób Linuksa wydaje się być niewiarygodnie szybki - być może System operacyjny pozwolił funkcji powrócić przed zakończeniem I / O? W każdym razie nie jest to wystarczająco przenośne dla wielu aplikacji.

EDIT : Ah, "natywny Linux" może poprawiać wydajność poprzez przeplatanie odczyt i zapis z asynchronicznym I / O. pozwalając komend stos w górę może pomóc sterownik dysku zdecydować, kiedy najlepiej szukać. Możesz spróbować zwiększyć Asio lub pthreads dla porównania. Co do "nie można pokonać deskryptorów plików POSIX" ... cóż, to prawda, jeśli robisz coś z danymi, a nie tylko ślepo kopiujesz.

 21
Author: Potatoswatter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-17 17:08:42

Chcę, aby Bardzo ważna Uwaga, że metoda Linuksa używająca sendfile () ma poważny problem, ponieważ nie może kopiować plików o rozmiarze większym niż 2GB! Zaimplementowałem go po tym pytaniu i zadawałem problemy, ponieważ używałem go do kopiowania plików HDF5 o rozmiarze wielu GB.

Http://man7.org/linux/man-pages/man2/sendfile.2.html

Sendfile () przeniesie co najwyżej 0x7ffff000 (2,147,479,552) bajtów, zwracanie liczby bajtów właściwie przeniesiony. (To prawda na zarówno 32-bitowe, jak i 64-bitowe systemy.)

 16
Author: rveale,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-29 12:33:40

Qt ma metodę kopiowania plików:

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

Zauważ, że aby go użyć, musisz zainstalować Qt (instrukcje tutaj) i dołączyć go do swojego projektu (jeśli używasz systemu Windows i nie jesteś administratorem, możesz pobrać Qt tutaj). Zobacz też tę odpowiedź .

 2
Author: Donald Duck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-16 20:40:10

Dla tych, którzy lubią boost:

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);

Zauważ, że boost::filesystem::pathjest również dostępny jako wpath dla Unicode. And that you could also use

using namespace boost::filesystem

Jeśli nie lubisz tych długich nazw typu

 1
Author: anhoppe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-02 09:40:16

Nie jestem do końca pewien, co to jest "dobry sposób" kopiowania pliku, ale zakładając, że "dobry" oznacza "szybki", mógłbym nieco rozszerzyć temat.

Obecne systemy operacyjne są od dawna zoptymalizowane pod kątem uruchamiania kopii pliku młyna. Żaden sprytny Kod tego nie przebije. Jest możliwe, że niektóre warianty technik kopiowania okażą się szybsze w niektórych scenariuszach testowych, ale najprawdopodobniej wypadłyby gorzej w innych przypadkach.

Zazwyczaj funkcja sendfile prawdopodobnie zwraca przed pisanie zostało popełnione, co sprawia wrażenie szybszego niż reszta. Nie czytałem kodu, ale jest to na pewno dlatego, że przydziela własny dedykowany bufor, wymieniając pamięć na czas. I powód, dla którego nie będzie działać dla plików większych niż 2Gb.

Tak długo, jak masz do czynienia z małą liczbą plików, wszystko dzieje się wewnątrz różnych buforów(najpierw środowisko uruchomieniowe C++, jeśli używasz iostream, wewnętrznych systemów operacyjnych, najwyraźniej dodatkowego bufora wielkości pliku w przypadku sendfile). Rzeczywisty nośnik pamięci masowej jest dostępny tylko po przeniesieniu wystarczającej ilości danych, aby było warto kręcić dyskiem twardym.

Przypuszczam, że można nieco poprawić wydajność w konkretnych przypadkach. Z czubka głowy:

  • jeśli kopiujesz ogromny plik na tym samym dysku, użycie bufora większego niż System Operacyjny może nieco poprawić sytuację(ale prawdopodobnie mówimy tu o gigabajtach).
  • jeśli chcesz skopiować ten sam plik na dwóch różnych fizycznych miejscach docelowych prawdopodobnie będzie to szybsze otwarcie trzech plików naraz niż wywołanie dwóch copy_file sekwencyjnie (choć prawie nie zauważysz różnicy, dopóki plik mieści się w pamięci podręcznej systemu operacyjnego)
  • Jeśli masz do czynienia z wieloma małymi plikami na dysku twardym, możesz chcieć je odczytać partiami, aby zminimalizować czas poszukiwania(chociaż system operacyjny już buforuje wpisy katalogu, aby uniknąć szukania jak szalone, a małe pliki prawdopodobnie i tak znacznie zmniejszą przepustowość dysku).

Ale wszystko, co jest poza zakresem funkcji kopiowania plików ogólnego przeznaczenia.

Tak więc moim zdaniem, doświadczony programista, kopia pliku C++ powinna używać dedykowanej funkcji C++17 file_copy, chyba że więcej wiadomo o kontekście, w którym występuje kopia pliku i można opracować sprytne strategie przechytrzenia systemu operacyjnego.

 1
Author: kuroi neko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-08 16:30:20