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
(또는_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 호출을 사용하고 출력 버퍼 크기를 실험 해보십시오. 전체 “다수 바이트”버퍼를 한 번에 전달하지 않고 몇 번의 쓰기 (즉, TotalNumBytes / OutBufferSize)를 수행해야합니다. OutBufferSize는 4096 바이트에서 메가 바이트까지 가능합니다.
다른 시도-WinAPI OpenFile / CreateFile을 사용 하고이 MSDN 기사 를 사용 하여 버퍼링을 해제 하십시오 (FILE_FLAG_NO_BUFFERING). 그리고 의 WriteFile에이 MSDN 문서 () 최적의 버퍼 크기를 알 수있는 드라이브의 블록 크기를 가져 오는 방법을 보여줍니다.
어쨌든 std :: ofstream은 래퍼이며 I / O 작업이 차단 될 수 있습니다. 전체 N- 기가 바이트 어레이를 순회하는 데에도 시간이 걸립니다. 작은 버퍼를 작성하는 동안 캐시에 도달하여 더 빠르게 작동합니다.