Programing

안전하고 효율적인 방법으로 파일 복사

lottogame 2020. 3. 20. 08:15
반응형

안전하고 효율적인 방법으로 파일 복사


파일 (이진 또는 텍스트)을 복사하는 좋은 방법을 찾고 있습니다. 몇 가지 샘플을 작성했는데 모두 작동합니다. 그러나 노련한 프로그래머의 의견을 듣고 싶습니다.

좋은 예제가 누락되었고 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은 이것을 "C 프로그래밍 언어"에서 사용함)

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

자신의 버퍼 C ++ 방법

#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 // 커널이 필요합니다> = 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;
}

환경

  • GNU / 리눅스 (Archlinux)
  • 커널 3.3
  • GLIBC-2.15, LIBSTDC ++ 4.7 (GCC-LIBS), GCC 4.7, 코어 유틸리티 8.16
  • RUNLEVEL 3 사용 (다중 사용자, 네트워크, 터미널, GUI 없음)
  • 인텔 SSD-Postville 80GB, 최대 50 % 채워짐
  • 270MB OGG-VIDEO-FILE 복사

재현 단계

 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

결과 (CPU TIME 사용)

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  

파일 크기는 변경되지 않습니다.
sha256sum은 동일한 결과를 인쇄합니다.
비디오 파일은 여전히 ​​재생할 수 있습니다.

질문

  • 어떤 방법을 선호하십니까?
  • 더 나은 솔루션을 알고 있습니까?
  • 내 코드에 실수가 있습니까?
  • 솔루션을 피해야하는 이유를 알고 있습니까?

  • FSTREAM (KISS, Streambuffer)
    정말 짧고 단순하기 때문에 이것을 좋아합니다. 지금까지 << 연산자가 rdbuf ()에 과부하되어 있고 아무것도 변환하지 않는다는 것을 알고 있습니다. 옳은?

감사

업데이트 1
모든 샘플에서 소스를 변경하여 파일 설명 자의 열기 및 닫기가 clock () 측정에 포함됩니다 . 그것들은 소스 코드에서 다른 중요한 변화가 아닙니다. 결과는 변하지 않습니다! 또한 결과를 다시 확인 하는 데 시간사용 했습니다 .

업데이트 2
ANSI C 샘플이 변경되었습니다. while 루프 의 조건은 더 이상 feof ()호출하지 않고 대신 fread () 를 조건으로 이동했습니다 . 코드가 10,000 클럭 더 빠르게 실행되는 것처럼 보입니다.

측정 변경 : 이전 명령 행 rm을 .ogv && sync && time ./program 각 프로그램에 대해 몇 번 반복했기 때문에 이전 결과는 항상 버퍼링 되었습니다. 이제 모든 프로그램에 대해 시스템을 재부팅합니다. 버퍼링되지 않은 결과는 새로운 것이며 놀라운 것은 아닙니다. 버퍼링되지 않은 결과는 실제로 변경되지 않았습니다.

이전 사본을 삭제하지 않으면 프로그램이 다르게 반응합니다. POSIX 및 SENDFILE을 사용하면 버퍼링 된 기존 파일을 덮어 쓰는 것이 빠르며 다른 모든 프로그램은 느려집니다. 옵션이 리거나 생성 되는 옵션 이이 동작에 영향을 줄 수 있습니다. 그러나 동일한 사본으로 기존 파일을 덮어 쓰는 것은 실제 사용 사례가 아닙니다.

cp로 복사를 수행하면 버퍼링되지 않은 0.44 초 및 버퍼링 된 0.30 초가 소요됩니다. 따라서 cp 는 POSIX 샘플보다 약간 느립니다. 나를 위해 잘 보인다.

아마도 mmap ()copy_file()boost :: filesystem 의 샘플과 결과도 추가 할 수 있습니다 .

업데이트 3
나는 이것을 블로그 페이지에도 넣고 약간 확장했습니다. Linux 커널의 저수준 함수 인 splice () 포함 아마도 자바가 더 많은 샘플이 뒤따를 것이다. http://www.ttyhoney.com/blog/?page_id=69


제정신으로 파일을 복사하십시오.

#include <fstream>

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

    dst << src.rdbuf();
}

이것은 매우 간단하고 직관적이며 추가 비용이 든다. 우리가 많이하고 있다면 파일 시스템에 대한 OS 호출로 넘어가는 것이 좋습니다. boost파일 시스템 클래스에 복사 파일 메소드가 있다고 확신 합니다.

파일 시스템과 상호 작용하는 C 방법이 있습니다.

#include <copyfile.h>

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

C ++ 17에서 파일을 복사하는 표준 방법은 <filesystem>헤더를 포함 하고 다음을 사용합니다.

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

첫 번째 형식은 copy_options::none옵션 으로 사용 된 두 번째 형식과 동일합니다 (참조 copy_file).

filesystem라이브러리는 처음으로 개발되었다 boost.filesystem마지막으로 C ++ 17과 같은 ++ ISO C에 합병했다.


너무 많아!

"ANSI C"방식 버퍼 FILE는 이미 버퍼링되어 있기 때문에 중복 됩니다. (이 내부 버퍼의 크기는 BUFSIZ실제로 정의한 것입니다.)

"OWN-BUFFER-C ++-WAY"는 fstream많은 가상 디스패치를 ​​수행하고 내부 버퍼 또는 각 스트림 객체를 다시 유지하면서 속도가 느려집니다 . streambuf_iterator클래스가 스트림 계층을 우회하므로 "COPY-ALGORITHM-C ++-WAY"는이 문제를 겪지 않습니다 .

"COPY-ALGORITHM-C ++-WAY"를 선호하지만을 구성하지 않고 실제 서식이 필요하지 않은 경우 fstream베어 std::filebuf인스턴스를 만듭니다 .

원시 성능을 위해 POSIX 파일 설명자를 이길 수 없습니다. 모든 플랫폼에서 추악하지만 휴대용이며 빠릅니다.

리눅스 방식은 엄청나게 빠르다. 아마도 OS가 I / O가 끝나기 전에 함수가 리턴하게 할까? 어쨌든 많은 응용 프로그램에 이식성이 충분하지 않습니다.

편집 : 아, "기본 Linux"는 비동기 I / O로 읽기 및 쓰기를 인터리빙하여 성능을 향상시킬 수 있습니다. 명령을 쌓아두면 디스크 드라이버가 가장 적합한시기를 결정하는 데 도움이됩니다. 비교를 위해 Boost Asio 또는 pthread를 사용해 볼 수 있습니다. "POSIX 파일 디스크립터를 이길 수 없습니다"에 관해서는 ... 맹목적으로 복사하는 것이 아니라 데이터로 무엇인가를하고 있다면 사실입니다.


내가 만들고 싶어 매우 리눅스 방법 사용에 sendfile ()는 그 크기가 2GB보다 더 많은 파일을 복사 할 수 있다는 점에서 큰 문제를 가지고하는 것이 중요 메모를! 나는이 질문에 따라 그것을 구현했으며 많은 GB 크기의 HDF5 파일을 복사하는 데 사용했기 때문에 문제가 발생했습니다.

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

sendfile ()은 최대 0x7ffff000 (2,147,479,552) 바이트를 전송하여 실제로 전송 된 바이트 수를 반환합니다. 이것은 32 비트 및 64 비트 시스템 모두에 해당됩니다.


Qt에는 파일을 복사하는 방법이 있습니다.

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

이것을 사용 하려면 Qt설치하고 ( 여기에 지시 사항 ) 프로젝트에 포함시켜야합니다 (Windows를 사용하고 관리자가 아닌 경우 대신 여기에서 Qt를 다운로드 할 수 있음 ). 이 답변 도 참조하십시오 .


부스트를 좋아하는 사람들을 위해 :

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

참고 부스트 : : 파일 시스템 :: 경로 도로 사용할 수 있습니다 wpath 유니 코드. 그리고 당신은 또한 사용할 수 있습니다

using namespace boost::filesystem

긴 유형 이름이 마음에 들지 않으면

참고 URL : https://stackoverflow.com/questions/10195343/copy-a-file-in-a-sane-safe-and-efficient-way

반응형