[c++] C ++로 바이너리 파일 작성하기

SSD (솔리드 스테이트 드라이브)에 방대한 양의 데이터를 쓰려고합니다. 그리고 엄청난 양의 80GB를 의미합니다.

솔루션을 웹에서 탐색했지만 가장 좋은 방법은 다음과 같습니다.

#include <fstream>
const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
    std::fstream myfile;
    myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    //Here would be some error handling
    for(int i = 0; i < 32; ++i){
        //Some calculations to fill a[]
        myfile.write((char*)&a,size*sizeof(unsigned long long));
    }
    myfile.close();
}

Visual Studio 2010 및 전체 최적화로 컴파일되고 Windows7에서 실행되는이 프로그램은 최대 약 20MB / s입니다. 정말 귀찮은 점은 Windows가 150MB / s와 200MB / s 사이의 다른 SSD 에서이 SSD로 파일을 복사 할 수 있다는 것입니다. 따라서 7 배 이상 빠릅니다. 그래서 더 빨리 갈 수 있어야한다고 생각합니다.

글쓰기 속도를 높이는 방법에 대한 아이디어가 있습니까?



답변

이것은 2012 년에 일을했습니다 :

#include <stdio.h>
const unsigned long long size = 8ULL*1024ULL*1024ULL;
unsigned long long a[size];

int main()
{
    FILE* pFile;
    pFile = fopen("file.binary", "wb");
    for (unsigned long long j = 0; j < 1024; ++j){
        //Some calculations to fill a[]
        fwrite(a, 1, size*sizeof(unsigned long long), pFile);
    }
    fclose(pFile);
    return 0;
}

36 초 만에 8GB의 시간을 가졌습니다. 약 220MB / s이며 SSD를 최대한 활용한다고 생각합니다. 또한 문제의 코드는 하나의 핵심 100 %를 사용했지만이 코드는 2-5 % 만 사용합니다.

모두에게 감사합니다.

업데이트 : 5 년이 지난 지금 2017 년이 지났습니다. 컴파일러, 하드웨어, 라이브러리 및 요구 사항이 변경되었습니다. 그래서 코드를 약간 변경하고 새로운 측정을 수행했습니다.

먼저 코드를 작성하십시오.

#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>

std::vector<uint64_t> GenerateData(std::size_t bytes)
{
    assert(bytes % sizeof(uint64_t) == 0);
    std::vector<uint64_t> data(bytes / sizeof(uint64_t));
    std::iota(data.begin(), data.end(), 0);
    std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() });
    return data;
}

long long option_1(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_2(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    auto startTime = std::chrono::high_resolution_clock::now();
    FILE* file = fopen("file.binary", "wb");
    fwrite(&data[0], 1, bytes, file);
    fclose(file);
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

long long option_3(std::size_t bytes)
{
    std::vector<uint64_t> data = GenerateData(bytes);

    std::ios_base::sync_with_stdio(false);
    auto startTime = std::chrono::high_resolution_clock::now();
    auto myfile = std::fstream("file.binary", std::ios::out | std::ios::binary);
    myfile.write((char*)&data[0], bytes);
    myfile.close();
    auto endTime = std::chrono::high_resolution_clock::now();

    return std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}

int main()
{
    const std::size_t kB = 1024;
    const std::size_t MB = 1024 * kB;
    const std::size_t GB = 1024 * MB;

    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option1, " << size / MB << "MB: " << option_1(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option2, " << size / MB << "MB: " << option_2(size) << "ms" << std::endl;
    for (std::size_t size = 1 * MB; size <= 4 * GB; size *= 2) std::cout << "option3, " << size / MB << "MB: " << option_3(size) << "ms" << std::endl;

    return 0;
}

이 코드는 Visual Studio 2017 및 g ++ 7.2.0 (새로운 요구 사항)으로 컴파일됩니다. 두 가지 설정으로 코드를 실행했습니다.

  • 노트북, Core i7, SSD, Ubuntu 16.04, -std = c ++ 11이있는 g ++ 버전 7.2.0 -march = native -O3
  • / Ox / Ob2 / Oi / Ot / GT / GL / Gy가 포함 된 데스크톱, Core i7, SSD, Windows 10, Visual Studio 2017 버전 15.3.1

다음과 같은 측정 결과를 얻었습니다 (1MB의 값을
버린 후에는 명백한 이상치이므로) :
option1과 option3 모두 SSD를 최대로 사용합니다. option2는 이전 컴퓨터에서 가장 빠른 코드 였기 때문에 이것을 보지 못했습니다.여기에 이미지 설명을 입력하십시오
여기에 이미지 설명을 입력하십시오

TL; DR : 내 측정 값이 std::fstream초과 사용 된 것으로 나타납니다 FILE.


답변

다음을 순서대로 시도하십시오.

  • 더 작은 버퍼 크기. 한 번에 ~ 2 MiB를 쓰는 것이 좋습니다. 마지막 랩톱에서 ~ 512 KiB가 좋은 위치 였지만 아직 SSD를 테스트하지 않았습니다.

    참고 : 매우 큰 버퍼는 성능 을 저하시키는 경향이 있음을 알았 습니다. 이전에 512-KiB 버퍼 대신 16-MiB 버퍼를 사용하여 속도 손실을 발견했습니다.

  • 파일을 열려면을 사용하십시오 _open(또는 _topenWindows가 올바른 경우) _write. 이것은 아마도 많은 버퍼링을 피할 것이지만 확실하지는 않습니다.

  • 사용하여 Windows 특정 기능을 좋아 CreateFile하고 WriteFile. 표준 라이브러리의 버퍼링을 피할 수 있습니다.


답변

std :: stream / FILE / device 사이에 차이가 없습니다. 버퍼링과 비 버퍼링 사이

참고 사항 :

  • SSD 드라이브는 가득 차면 속도가 느려져 “전송 속도가 느려집니다”.
  • SSD 드라이브는 작동하지 않는 비트로 인해 오래 될수록 속도가 느려지는 경향이 있습니다 (전송 속도가 느림).

63 초 안에 코드가 실행되는 것을보고 있습니다.
따라서 전송 속도 : 260M / s (내 SSD는 사용자보다 약간 빠릅니다).

64 * 1024 * 1024 * 8 /*sizeof(unsigned long long) */ * 32 /*Chunks*/

= 16G
= 16G/63 = 260M/s

std :: fstream에서 FILE *로 이동하면 아무런 증가가 없습니다.

#include <stdio.h>

using namespace std;

int main()
{

    FILE* stream = fopen("binary", "w");

    for(int loop=0;loop < 32;++loop)
    {
         fwrite(a, sizeof(unsigned long long), size, stream);
    }
    fclose(stream);

}

따라서 C ++ 스트림은 기본 라이브러리가 허용하는 한 빨리 작동합니다.

그러나 OS를 OS 위에 구축 된 응용 프로그램과 비교하는 것은 불공평하다고 생각합니다. 응용 프로그램은 어떤 가정도 할 수 없으며 (드라이브가 SSD인지 알 수 없음) 따라서 OS의 파일 메커니즘을 사용하여 전송합니다.

OS는 어떤 가정도 할 필요가 없습니다. 관련된 드라이브 유형을 알려주고 데이터 전송을위한 최적의 기술을 사용할 수 있습니다. 이 경우 메모리를 메모리로 직접 전송하십시오. 메모리의 한 위치에서 다른 위치로 80G를 복사하는 프로그램을 작성하여 얼마나 빠른지보십시오.

편집하다

더 낮은 수준의 호출을 사용하도록 코드를 변경했습니다.
즉 버퍼링이 없습니다.

#include <fcntl.h>
#include <unistd.h>


const unsigned long long size = 64ULL*1024ULL*1024ULL;
unsigned long long a[size];
int main()
{
    int data = open("test", O_WRONLY | O_CREAT, 0777);
    for(int loop = 0; loop < 32; ++loop)
    {
        write(data, a, size * sizeof(unsigned long long));
    }
    close(data);
}

이것은 차이가 없었습니다.

참고 : 일반 드라이브가있는 경우 내 드라이브는 SSD 드라이브입니다. 위의 두 기술간에 차이가있을 수 있습니다. 그러나 비 버퍼링 및 버퍼링 (버퍼 크기보다 큰 청크를 쓸 때)은 아무런 차이가 없습니다.

편집 2 :

C ++에서 파일을 복사하는 가장 빠른 방법을 사용해 보셨습니까?

int main()
{
    std::ifstream  input("input");
    std::ofstream  output("ouptut");

    output << input.rdbuf();
}


답변

가장 좋은 솔루션은 이중 버퍼링으로 비동기 쓰기를 구현하는 것입니다.

타임 라인을보십시오 :

------------------------------------------------>
FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|FF|WWWWWWWW|

‘F’는 버퍼를 채우는 시간을 나타내고 ‘W’는 디스크에 버퍼를 쓰는 시간을 나타냅니다. 따라서 버퍼를 파일에 쓰는 데 시간을 낭비하는 문제가 있습니다. 그러나 별도의 스레드에 쓰기를 구현하면 다음과 같이 바로 다음 버퍼를 채울 수 있습니다.

------------------------------------------------> (main thread, fills buffers)
FF|ff______|FF______|ff______|________|
------------------------------------------------> (writer thread)
  |WWWWWWWW|wwwwwwww|WWWWWWWW|wwwwwwww|

F-첫 번째 버퍼 채우기
f-두 번째 버퍼 채우기
W-첫 번째 버퍼를 파일에
쓰는 중 w-두 번째 버퍼를 파일에 쓰는 중 _-
작업이 완료되는 동안 대기

버퍼 스왑을 사용한이 접근 방식은 버퍼를 채우는 데 더 복잡한 계산이 필요하므로 시간이 더 많이 걸립니다. 나는 항상 내부에 비동기 쓰기를 숨기는 CSequentialStreamWriter 클래스를 구현하므로 최종 사용자에게는 인터페이스에 Write 함수가 있습니다.

버퍼 크기는 디스크 클러스터 크기의 배수 여야합니다. 그렇지 않으면 2 개의 인접 디스크 클러스터에 단일 버퍼를 작성하여 성능이 저하됩니다.

마지막 버퍼 쓰기
마지막으로 쓰기 기능을 호출 할 때 현재 버퍼가 채워지고 있는지 디스크에 기록해야합니다. 따라서 CSequentialStreamWriter에는 데이터의 마지막 부분을 디스크에 기록해야하는 Finalize (최종 버퍼 플러시)라고하는 별도의 메서드가 있어야합니다.

오류 처리.
코드가 두 번째 버퍼를 채우고 첫 번째 버퍼가 별도의 스레드에서 작성되고 있지만 어떤 이유로 쓰기가 실패하는 동안 주 스레드는 해당 오류를 인식해야합니다.

------------------------------------------------> (main thread, fills buffers)
FF|fX|
------------------------------------------------> (writer thread)
__|X|

CSequentialStreamWriter의 인터페이스에 Write 함수가 bool을 반환하거나 예외를 throw하여 별도의 스레드에 오류가 있다고 가정하고 그 상태를 기억해야하므로 다음 번 기본 스레드에서 Write 또는 Finilize를 호출하면 메소드가 반환됩니다 거짓이거나 예외를 던질 것입니다. 그리고 실패 후에 데이터를 미리 쓴 경우에도 버퍼 채우기를 중단 한 시점은 중요하지 않습니다. 대부분 파일이 손상되어 쓸모가 없습니다.


답변

파일 매핑을 시도하는 것이 좋습니다 . 내가 사용하는 mmapUNIX 환경에서, 과거에, 그리고 나는 내가 얻을 수있는 높은 성능에 깊은 인상을 받았습니다


답변

FILE*대신 사용할 수 있고 얻은 성능을 측정 할 수 있습니까? 몇 가지 옵션은 다음 fwrite/write대신 사용하는 것입니다 fstream.

#include <stdio.h>

int main ()
{
  FILE * pFile;
  char buffer[] = { 'x' , 'y' , 'z' };
  pFile = fopen ( "myfile.bin" , "w+b" );
  fwrite (buffer , 1 , sizeof(buffer) , pFile );
  fclose (pFile);
  return 0;
}

를 사용하기로 결정한 경우 write비슷한 것을 시도하십시오.

#include <unistd.h>
#include <fcntl.h>

int main(void)
{
    int filedesc = open("testfile.txt", O_WRONLY | O_APPEND);

    if (filedesc < 0) {
        return -1;
    }

    if (write(filedesc, "This will be output to testfile.txt\n", 36) != 36) {
        write(2, "There was an error writing to testfile.txt\n", 43);
        return -1;
    }

    return 0;
}

또한 살펴 보라고 조언합니다 memory map. 그것은 당신의 대답 일 수 있습니다. 일단 데이터베이스에 저장하기 위해 20GB 파일을 다른 파일로 처리해야했고 파일이 열리지 않았습니다. 그래서 moemory map을 활용하는 솔루션입니다. 그래도 그렇게했습니다 Python.


답변

open () / write () / close () API 호출을 사용하고 출력 버퍼 크기를 실험 해보십시오. 전체 “다수 바이트”버퍼를 한 번에 전달하지 않고 몇 번의 쓰기 (즉, TotalNumBytes / OutBufferSize)를 수행해야합니다. OutBufferSize는 4096 바이트에서 메가 바이트까지 가능합니다.

다른 시도-WinAPI OpenFile / CreateFile을 사용 하고이 MSDN 기사 를 사용 하여 버퍼링을 해제 하십시오 (FILE_FLAG_NO_BUFFERING). 그리고 의 WriteFile에이 MSDN 문서 () 최적의 버퍼 크기를 알 수있는 드라이브의 블록 크기를 가져 오는 방법을 보여줍니다.

어쨌든 std :: ofstream은 래퍼이며 I / O 작업이 차단 될 수 있습니다. 전체 N- 기가 바이트 어레이를 순회하는 데에도 시간이 걸립니다. 작은 버퍼를 작성하는 동안 캐시에 도달하여 더 빠르게 작동합니다.