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


305

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

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


5
fstream확실히 파일 작업에 좋은 옵션입니다.
chris


28
당신은 게으른 길을 잊었다 : system ( "cp from.ogv to.ogv");
fbafelipe

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

3
칩핑이 너무 늦어서 죄송하지만 오류 처리가 없으므로 '안전'이라고 설명하지 않습니다.
Richard Kettlewell

답변:


259

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

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

28
copyfile휴대용이 아닙니다. 맥 오에스텐에만 해당되는 것 같다. 확실히 리눅스에는 존재하지 않는다. boost::filesystem::copy_file아마도 기본 파일 시스템을 통해 파일을 복사하는 가장 쉬운 방법 일 것입니다.
Mike Seymour

4
@ MikeSeymour : copyfile ()은 BSD 확장 인 것 같습니다.
Martin York

10
@ duedl0r : 아니요. 객체에는 소멸자가 있습니다. 스트림의 소멸자는 자동으로 close ()를 호출합니다. codereview.stackexchange.com/q/540/507
Martin York

11
@ duedl0r : 예. 그러나 그것은 "해가지면"이라고 말하는 것과 같습니다. 당신은 정말 빨리 서쪽으로 달릴 수 있고 당신은 하루를 약간 길게 만들지 만 태양은지고있을 것입니다. 버그 및 누수 메모리가없는 한 (범위를 벗어남). 그러나 동적 메모리 관리가 없기 때문에 누출이 없으며 범위가 벗어날 것입니다 (태양이 설정되는 것처럼).
Martin York

6
그런 다음 {} 스코프 블록
paulm

62

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 ++에 병합되었습니다.


2
?와 같은 기본 인수를 가진 단일 함수가없는 이유는 무엇 bool copy_file( const std::filesystem::path& from, const std::filesystem::path& to, std::filesystem::copy_options options = std::filesystem::copy_options::none);입니까?
Jepessen

2
@Jepessen 나는 이것에 대해 확신하지 못한다. 아마 중요 하지 않을 수도 있습니다 .
manlio

표준 라이브러리의 @Jepessen은 깨끗한 코드가 가장 중요합니다. 과부하 (기본 매개 변수가있는 하나의 기능과 반대)가 있으면 프로그래머의 의도가 더 명확합니다.
Marc.2377

@Peter C ++ 17을 사용할 수 있다는 점에서 이것이 아마도 받아 들여질 것입니다.
마틴 요크

21

너무 많아!

"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 파일 디스크립터를 이길 수 없습니다"에 관해서는 ... 맹목적으로 복사하는 것이 아니라 데이터로 무엇인가를하고 있다면 사실입니다.


ANSI C : 그러나 함수에 fread / fwrite 크기를 주어야합니까? pubs.opengroup.org/onlinepubs/9699919799/toc.htm
Peter

@PeterWeber 글쎄, BUFSIZ는 그 어느 때보 다 좋은 가치이며 사실 한 번에 한 문자 또는 "몇 몇"문자에 비해 속도가 빨라질 것입니다. 어쨌든 성능 측정은 어떤 경우에도 최선의 방법이 아니라는 것을 나타냅니다.
Potatoswatter

1
나는 이것에 대해 깊이 이해하지 않았으므로 가정과 의견에주의해야합니다. Linux-Way는 Kernelspace afaik에서 실행됩니다. 이것은 커널 공간과 사용자 공간 사이의 느린 문맥 전환을 피해야합니까? 내일 나는 sendfile 맨 페이지를 다시 살펴볼 것이다. 얼마 전에 Linus Torvalds는 많은 작업에 Userspace-Filesystems을 좋아하지 않는다고 말했습니다. 아마도 sendfile이 그의 견해에 긍정적 인 예일까요?
Peter

5
" sendfile()한 파일 디스크립터와 다른 파일 디스크립터 사이에 데이터를 복사합니다.이 복사는 커널 내에서 수행되므로 사용자 공간과의 데이터 전송이 필요한 및 sendfile()의 조합보다 효율적 입니다.": kernel.org/doc/man-pages /online/pages/man2/sendfile.2.htmlread(2)write(2)
Max

1
원시 filebuf객체 를 사용하는 예를 게시 할 수 있습니까?
Kerrek SB

14

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

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

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


1
sendfile64 () 같은 문제가 있습니까?
graywolf

1
@Paladin sendfile64는 이러한 한계를 극복하기 위해 개발 된 것 같습니다. 맨 페이지에서 : "" "원본 Linux sendfile () 시스템 호출은 큰 파일 오프셋을 처리하도록 설계되지 않았으므로 Linux 2.4는 sendfile64 ()를 오프셋 인수에 대해 더 넓은 유형으로 추가했습니다 .gligli sendfile () 래퍼 함수 "" "
rveale

sendfile64는 같은 문제가 있습니다. 그러나 오프셋 유형 off64_t을 사용 하면 링크 된 질문 에 대한 답변 과 같이 루프를 사용하여 큰 파일을 복사 할 수 있습니다 .
pcworld

'mandfile'에 대한 성공적인 호출은 요청 된 것보다 적은 바이트를 쓸 수 있습니다. 발신자가 바이트가 없으면 호출을 다시 시도 할 준비가되어 있어야합니다. ' sendfile 또는 sendfile64는 전체 복사가 완료 될 때까지 루프 내에서 호출해야 할 수도 있습니다.
philippe lhardy

2

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

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

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


1
QFile::copy4k 버퍼링 때문에 엄청나게 느립니다 .
Nicolas Holthaus

1
의 최신 버전에서는 속도 저하가 수정되었습니다 Qt. 나는 사용 5.9.2하고 있으며 속도는 기본 구현과 동등합니다. Btw. 소스 코드를 살펴보면 Qt는 실제로 기본 구현을 호출하는 것 같습니다.
VK

1

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

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

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


Boost의 파일 시스템 라이브러리는 컴파일이 필요한 예외 중 하나입니다. 참고로!
SimonC

0

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

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

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

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

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

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

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

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.