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 배 이상 빠릅니다. 그래서 더 빨리 갈 수 있어야한다고 생각합니다.
글쓰기 속도를 높이는 방법에 대한 아이디어가 있습니까?
이것은 일을했다 :
#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 년이 지났습니다. 컴파일러, 하드웨어, 라이브러리 및 요구 사항이 변경되었습니다. 그래서 코드를 일부 변경하고 측정을 수행했습니다.
먼저 코드를 작성하십시오.
#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
(또는_topen
Windows가 올바른 경우)_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를 호출하면 메소드가 반환됩니다 거짓이거나 예외를 던질 것입니다. 그리고 실패 후에 데이터를 미리 쓴 경우에도 버퍼 채우기를 중단 한 시점은 중요하지 않습니다. 대부분 파일이 손상되어 쓸모가 없습니다.
파일 매핑을 시도하는 것이 좋습니다 . 내가 사용하는 mmap
UNIX 환경에서, 과거에, 그리고 나는 내가 얻을 수있는 높은 성능에 깊은 인상을 받았습니다
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 호출을 사용하고 출력 버퍼 크기를 실험 해보십시오. 전체 "many-many-bytes"버퍼를 한 번에 전달하지 않고 몇 번의 쓰기 (예 : TotalNumBytes / OutBufferSize)를 수행해야합니다. OutBufferSize는 4096 바이트에서 메가 바이트까지 가능합니다.
다른 시도-WinAPI OpenFile / CreateFile을 사용 하고이 MSDN 기사 를 사용 하여 버퍼링을 해제 하십시오 (FILE_FLAG_NO_BUFFERING). 그리고 의 WriteFile에이 MSDN 문서 () 최적의 버퍼 크기를 알 수있는 드라이브의 블록 크기를 가져 오는 방법을 보여줍니다.
어쨌든 std :: ofstream은 래퍼이며 I / O 작업이 차단 될 수 있습니다. 전체 N- 기가 바이트 어레이를 순회하는 데에도 시간이 걸립니다. 작은 버퍼를 작성하는 동안 캐시에 도달하여 더 빠르게 작동합니다.
메모리 매핑 파일을 사용하십시오.
탐색기에서 디스크 A에서 디스크 B로 무언가를 복사하면 Windows는 DMA를 사용합니다. 즉, 대부분의 복사 프로세스에서 CPU는 기본적으로 디스크 컨트롤러에 데이터를 어디에 넣을 것인지, 데이터를 가져 와서 체인의 전체 단계를 없애고 대량 이동에 최적화되지 않은 것 외에는 아무것도하지 않습니다. 데이터-하드웨어를 의미합니다.
무엇 당신이 할 것은 CPU를 많이 포함한다. "[]을 (를) 채우는 일부 계산"을 알려 드리고자합니다. 나는 필수적이라고 생각합니다. a []를 생성 한 다음 a []에서 출력 버퍼로 복사 한 다음 (fstream :: write의 기능) 다시 생성합니다.
무엇을해야합니까? 멀티 스레딩! (멀티 코어 프로세서가 있기를 바랍니다)
- 포크.
- 하나의 스레드를 사용하여 [] 데이터 생성
- 다른 하나를 사용하여 a []에서 디스크로 데이터 쓰기
- 두 개의 배열 a1 [] 및 a2 []가 필요하며 이들 사이를 전환합니다.
- 스레드 (세마포어, 메시지 큐 등)간에 일종의 동기화가 필요합니다.
- Mehrdad가 언급 한 WriteFile 함수 와 같은 낮은 레벨의 버퍼되지 않은 함수를 사용하십시오.
fstream
s는 C 스트림보다 느리지 않지만 더 많은 CPU 를 사용합니다 (특히 버퍼링이 올바르게 구성되지 않은 경우). CPU가 포화되면 I / O 속도가 제한됩니다.
최소한 MSVC 2015 구현 은 스트림 버퍼가 설정되지 않은 경우 한 번 에 1 개의 문자를 출력 버퍼에 복사 합니다 (참조 streambuf::xsputn
). 따라서 스트림 버퍼 (> 0)를 설정하십시오 .
fstream
이 코드 를 사용하여 쓰기 속도 1500MB / s (M.2 SSD의 최고 속도)를 얻을 수 있습니다 .
#include <iostream>
#include <fstream>
#include <chrono>
#include <memory>
#include <stdio.h>
#ifdef __linux__
#include <unistd.h>
#endif
using namespace std;
using namespace std::chrono;
const size_t sz = 512 * 1024 * 1024;
const int numiter = 20;
const size_t bufsize = 1024 * 1024;
int main(int argc, char**argv)
{
unique_ptr<char[]> data(new char[sz]);
unique_ptr<char[]> buf(new char[bufsize]);
for (size_t p = 0; p < sz; p += 16) {
memcpy(&data[p], "BINARY.DATA.....", 16);
}
unlink("file.binary");
int64_t total = 0;
if (argc < 2 || strcmp(argv[1], "fopen") != 0) {
cout << "fstream mode\n";
ofstream myfile("file.binary", ios::out | ios::binary);
if (!myfile) {
cerr << "open failed\n"; return 1;
}
myfile.rdbuf()->pubsetbuf(buf.get(), bufsize); // IMPORTANT
for (int i = 0; i < numiter; ++i) {
auto tm1 = high_resolution_clock::now();
myfile.write(data.get(), sz);
if (!myfile)
cerr << "write failed\n";
auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
cout << tm << " ms\n";
total += tm;
}
myfile.close();
}
else {
cout << "fopen mode\n";
FILE* pFile = fopen("file.binary", "wb");
if (!pFile) {
cerr << "open failed\n"; return 1;
}
setvbuf(pFile, buf.get(), _IOFBF, bufsize); // NOT important
auto tm1 = high_resolution_clock::now();
for (int i = 0; i < numiter; ++i) {
auto tm1 = high_resolution_clock::now();
if (fwrite(data.get(), sz, 1, pFile) != 1)
cerr << "write failed\n";
auto tm = (duration_cast<milliseconds>(high_resolution_clock::now() - tm1).count());
cout << tm << " ms\n";
total += tm;
}
fclose(pFile);
auto tm2 = high_resolution_clock::now();
}
cout << "Total: " << total << " ms, " << (sz*numiter * 1000 / (1024.0 * 1024 * total)) << " MB/s\n";
}
나는 다른 플랫폼에서이 코드 (우분투, FreeBSD의)를 시도에는 I / O 속도의 차이를 발견 없지만, CPU 사용량 1 (8 약의 차이는 fstream
사용 8 배 더 많은 CPU를 ). 따라서 디스크 fstream
속도가 더 빠르면 쓰기 속도가 stdio
버전 보다 빨리 느려질 수 있습니다.
파일 스트림에 빠르게 쓰려면 읽기 버퍼를 더 크게 만들 수 있습니다.
wfstream f;
const size_t nBufferSize = 16184;
wchar_t buffer[nBufferSize];
f.rdbuf()->pubsetbuf(buffer, nBufferSize);
또한 많은 양의 데이터를 파일에 기록 할 때 파일을 논리적으로 확장 할 때 파일 크기 를 논리적으로 확장하는 것이 더 빠를 수 있습니다. 파일 을 논리적으로 확장 할 때 파일 시스템은 파일을 쓰기 전에 새 공간을 0으로 만들지 않기 때문입니다. 또한 파일 확장을 막기 위해 실제로 필요한 것보다 파일을 논리적으로 확장하는 것이 좋습니다. 논리 파일 확장자 호출하여 Windows에서 지원 SetFileValidData
또는 xfsctl
로 XFS_IOC_RESVSP64
XFS 시스템.
내 프로그램을 GNU / Linux 에서 gcc로 , mingw 에서 win 7로, xp로 승리하고 잘 작동했습니다.
내 프로그램을 사용하고 80GB 파일을 만들려면 33 줄을 다음과 같이 변경하십시오.
makeFile("Text.txt",1024,8192000);
프로그램을 종료하면 파일이 삭제되고 파일이 실행될 때 파일을 확인합니다
원하는 프로그램을 변경하기 만하면됩니다
firt 하나는 windows 프로그램이고 두 번째는 GNU / Linux입니다.
http://mustafajf.persiangig.com/Projects/File/WinFile.cpp
http://mustafajf.persiangig.com/Projects/File/File.cpp
참고 URL : https://stackoverflow.com/questions/11563963/writing-a-binary-file-in-c-very-fast
'Programing' 카테고리의 다른 글
classpath 디렉토리에서 자원 목록 가져 오기 (0) | 2020.04.23 |
---|---|
window.onload 대 (0) | 2020.04.23 |
의미 = 함수 선언 후 삭제 (0) | 2020.04.23 |
express.Router와 app.get의 차이점은 무엇입니까? (0) | 2020.04.23 |
람다 이벤트 핸들러를 제거하는 방법 (0) | 2020.04.23 |