안전하고 효율적인 방법으로 파일 복사 write, close #include <cstdio> //

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

좋은 예제가 누락되었고 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.filesystemC ++ 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

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


답변

파일을 복사하는 “좋은 방법”이 무엇인지 잘 모르겠지만 “좋음”이 “빠른”을 의미한다고 가정하면 주제를 조금 넓힐 수 있습니다.

현재 운영 체제는 오랫동안 밀 파일 복사본 실행을 처리하도록 최적화되었습니다. 영리한 코드가 이길 수는 없습니다. 일부 테스트 시나리오에서는 일부 변형 복사 기술이 더 빨리 입증 될 수 있지만 다른 경우에는 더 나빠질 수 있습니다.

일반적으로 sendfile 쓰기가 커밋되기 전에 함수가 반환되어 나머지 것보다 빠르다는 인상을 줄 수 있습니다. 코드를 읽지 못했지만 시간이 지남에 따라 메모리를 거래하는 자체 전용 버퍼를 할당하기 때문에 가장 확실합니다. 그리고 2Gb보다 큰 파일에서는 작동하지 않는 이유입니다.

적은 수의 파일을 다루는 한 모든 것은 다양한 버퍼 내부에서 발생합니다 (C ++ 런타임 iostream은 OS 내부 파일 을 사용하는 경우 첫 번째입니다. 경우 에는 파일 크기의 여분 버퍼 sendfile). 실제 스토리지 미디어는 하드 디스크를 회전시키는 데 어려움을 겪을만큼 충분한 데이터가 이동 된 후에 만 ​​액세스됩니다.

특정 경우 성능을 약간 향상시킬 수 있다고 가정합니다. 내 머리 꼭대기에서 :

  • 동일한 디스크에 큰 파일을 복사하는 경우 OS보다 큰 버퍼를 사용하면 약간 개선 될 수 있습니다 (그러나 여기서는 기가 바이트에 대해 이야기하고있을 것입니다).
  • 두 개의 서로 다른 물리적 대상에 동일한 파일을 복사하려면 copy_file순차적으로 두 개를 호출하는 것보다 한 번에 세 개의 파일을 여는 것이 더 빠를 것입니다 (파일이 OS 캐시에있는 한 차이를 거의 느끼지 못합니다)
  • HDD에서 작은 파일을 많이 처리하는 경우 탐색 시간을 최소화하기 위해 파일을 일괄 적으로 읽을 수 있습니다 (OS는 이미 디렉토리 항목을 캐시하여 미친 파일과 같은 파일을 찾지 않기 때문에 디스크 대역폭을 크게 줄일 수 있습니다).

그러나 모든 것이 범용 파일 복사 기능의 범위를 벗어납니다.

따라서 필자의 노련한 프로그래머의 견해로는 C ++ 파일 사본은 C ++ 17 file_copy전용 함수를 사용해야합니다 . 파일 복사가 발생하는 컨텍스트에 대해 더 많이 알지 못하고 일부 현명한 전략을 사용하여 OS보다 우월합니다.