잠재적으로 100GB 이상 크기의 파일을 처리하는 프로그램을 개발 중입니다. 파일에는 가변 길이 레코드 세트가 포함됩니다. 첫 번째 구현을 시작하여 실행 중이며 특히 입력 파일이 여러 번 스캔되므로 I / O를보다 효율적으로 수행하는 데 성능을 향상시키는 방향으로 찾고 있습니다.
mmap()
C ++의 fstream
라이브러리 를 통해 블록 을 사용하거나 읽는 것에 대한 경험 규칙이 있습니까? 내가하고 싶은 것은 디스크에서 버퍼로 큰 블록을 읽고 버퍼에서 완전한 레코드를 처리 한 다음 더 읽으십시오.
mmap()
코드는 잠재적으로 매우부터 지저분한 얻을 수있는 mmap
D 블록은 페이지 경계 (내 이해) 크기에 거짓말에 필요한 ‘및 기록 할 수 잠재적에서 같은 페이지 경계. fstream
s를 사용하면 페이지 크기 경계에있는 블록을 읽는 것에 만 국한되지 않기 때문에 레코드의 시작을 찾고 다시 읽을 수 있습니다.
실제로 전체 구현을 작성하지 않고이 두 옵션 중에서 어떻게 결정할 수 있습니까? 경험적 규칙 (예 : mmap()
2 배 빠름) 또는 간단한 테스트?
답변
Linux에서 mmap / read performance에 대한 최종 단어를 찾으려고 노력 했으며 Linux 커널 메일 링리스트에서 멋진 게시물 ( link )을 발견했습니다. 그것은 2000, 그래서 IO 및 그 이후 커널의 가상 메모리에 많은 개선이 있었다, 그러나 잘하는 이유에 대해 설명 mmap
또는 read
빠르거나 느려질 수 있습니다합니다.
- 이 호출에
mmap
보다 오버 갖는다read
(처럼가epoll
더 이상 오버 갖는poll
오버 헤드보다 갖는,read
). 가상 메모리 매핑을 변경하는 것은 다른 프로세스 간을 전환하는 데 비용이 많이 드는 것과 같은 이유로 일부 프로세서에서 상당히 비싼 작업입니다. - IO 시스템은 이미 디스크 캐시를 사용할 수 있으므로 파일을 읽으면 사용하는 방법에 관계없이 캐시에 도달하거나 누락됩니다.
하나,
- 특히 액세스 패턴이 희박하고 예측할 수없는 경우 메모리 맵이 임의 액세스에 더 빠릅니다.
- 메모리 맵을 사용하면 완료 할 때까지 캐시에서 페이지 를 계속 사용할 수 있습니다 . 즉, 파일을 오랜 시간 동안 많이 사용한 다음 닫았다가 다시 열면 페이지가 계속 캐시됩니다. 을 (를) 사용하면
read
파일이 이전 캐시에서 플러시되었을 수 있습니다. 파일을 사용하여 즉시 버리는 경우에는 적용되지 않습니다. (mlock
페이지를 캐시에 보관하기 위해 페이지 를 시도 하면 디스크 캐시를 능가하려고하지만 이런 종류의 어리 석음은 시스템 성능에 거의 도움이되지 않습니다). - 파일을 직접 읽는 것은 매우 간단하고 빠릅니다.
mmap / read에 대한 토론은 두 가지 다른 성능 토론을 상기시킵니다.
-
일부 Java 프로그래머는 비 차단 I / O가 종종 I / O를 차단하는 것보다 느리다는 사실에 충격을 받았습니다.
-
일부 다른 네트워크 프로그래머는 것을 알고 충격을 받았다
epoll
종종 느린보다poll
당신이 관리가 알고있는 경우에 완벽한 이해하게하는,epoll
더 콜을 필요합니다.
결론 : 데이터에 무작위로 액세스하거나 오랫동안 보관하거나 다른 프로세스와 공유 할 수있는 경우 메모리 맵을 사용하십시오 ( MAP_SHARED
실제 공유가없는 경우에는 그리 흥미롭지 않습니다). 데이터에 순차적으로 액세스하거나 읽은 후 폐기하면 파일을 정상적으로 읽습니다. 두 방법 중 어느 방법으로도 프로그램이 덜 복잡해지면 그렇게하십시오 . 많은 실제 사례에서 벤치 마크가 아닌 실제 응용 프로그램을 테스트하지 않고도 더 빠른 방법을 보여줄 수있는 확실한 방법이 없습니다.
(이 질문에 대해 죄송하지만 답변을 찾고 있었고이 질문은 Google 결과의 최상위에 계속 올라 왔습니다.)
답변
주요 성능 비용은 디스크 I / O입니다. “mmap ()”은 확실히 istream보다 빠르지 만 디스크 i / o가 런타임을 지배하므로 차이가 눈에 띄지 않을 수 있습니다.
나는 (아래 / 위 참조) “의 mmap ()는 것을 자신의 주장을 테스트하는 벤 콜린스의 코드 조각을 시도 방법은 빨리”와 측정 가능한 차이를 찾을 수 없습니다. 그의 답변에 대한 내 의견을 참조하십시오.
나는 확실히 것 없는 당신의 “기록”거대한하지 않는 한 별도로 차례로 각 레코드를 mmap 할 추천 – 그 끔찍하게 느린 것, 각 레코드에 대해이 시스템 호출을 필요로하고 가능한 디스크 메모리 캐시에서 페이지를 잃고 …. .
귀하의 경우 mmap (), istream 및 저수준 open () / read () 호출은 모두 거의 동일하다고 생각합니다. 이 경우 mmap ()을 권장합니다.
- 파일 내에 무작위 액세스 (순차 아님)가 있으며
- 모든 것이 메모리에 편안하게 맞거나 파일 내에 참조 위치가 있으므로 특정 페이지를 매핑하고 다른 페이지를 매핑 할 수 있습니다. 그렇게하면 운영 체제는 사용 가능한 RAM을 사용하여 최대의 이점을 얻습니다.
- 또는 여러 프로세스가 동일한 파일에서 읽고 작동하는 경우 프로세스가 모두 동일한 실제 페이지를 공유하므로 mmap ()은 환상적입니다.
(btw-mmap () / MapViewOfFile ()을 좋아합니다).
답변
의 mmap는 방법 빨리. 간단한 벤치 마크를 작성하여 스스로 증명할 수 있습니다.
char data[0x1000];
std::ifstream in("file.bin");
while (in)
{
in.read(data, 0x1000);
// do something with data
}
대:
const int file_size=something;
const int page_size=0x1000;
int off=0;
void *data;
int fd = open("filename.bin", O_RDONLY);
while (off < file_size)
{
data = mmap(NULL, page_size, PROT_READ, 0, fd, off);
// do stuff with data
munmap(data, page_size);
off += page_size;
}
분명히, 나는 (예 page_size
를 들어 파일이 배수가 아닌 경우 파일 끝에 도달 할 때를 결정하는 방법과 같은) 세부 정보를 생략 하지만 실제로는 이것보다 훨씬 복잡하지 않아야합니다 .
가능하면 데이터를 부분이 아닌 전체적으로 mmap () 가능한 여러 파일로 분할하려고 할 수 있습니다 (훨씬 더 간단 함).
몇 달 전에 나는 boost_iostreams에 대한 슬라이딩 창 mmap () 기반 스트림 클래스를 반 구운 구현했지만 아무도 신경 쓰지 않았고 다른 것들로 바빴습니다. 불행히도, 나는 몇 주 전에 완성되지 않은 오래된 프로젝트의 아카이브를 삭제했으며 그것은 희생자 중 하나였습니다.
업데이트 : Microsoft가 처음에 mmap으로 수행하는 대부분의 작업을 수행하는 멋진 파일 캐시를 구현했기 때문에 Windows 에서이 벤치 마크가 상당히 다르게 보일 것이라는 경고를 추가해야합니다. 즉, 자주 액세스하는 파일의 경우 std :: ifstream.read ()를 수행하면 파일 캐시가 이미 메모리 매핑을 수행했기 때문에 mmap만큼 빠르며 투명합니다.
최종 업데이트 : Look, people : OS와 표준 라이브러리, 디스크 및 메모리 계층의 다양한 플랫폼 조합 mmap
에서 블랙 박스로 보는 시스템 호출 이 항상 항상 실질적으로 더 빠를 것이라고 말할 수는 없습니다. 보다 read
. 내 말이 그런 식으로 해석 될 수 있더라도 그것은 나의 의도가 아니었다. 궁극적으로 필자의 요점은 메모리 매핑 된 i / o가 일반적으로 바이트 기반 i / o보다 빠르다는 것입니다. 이것은 여전히 사실 입니다. 실험적으로 둘 사이에 차이가 없다는 것을 알게되면 나에게 합리적 인 유일한 설명은 귀하의 플랫폼이 덮개 아래에서 호출을 수행하는 데 유리한 방식으로 메모리 매핑을 구현한다는 것입니다read
입니다. 휴대용으로 메모리 매핑 된 I / O를 사용하고 있는지 확실하게 확인할 수있는 유일한 방법은을 사용하는 것 mmap
입니다. 이식성에 신경 쓰지 않고 대상 플랫폼의 특정 특성에 의존 할 수 read
있다면 성능을 크게 저하시키지 않고 사용하는 것이 적합 할 수 있습니다.
답변 목록을 정리하려면 편집 :
@jbl :
슬라이딩 윈도우 mmap이 흥미롭게 들립니다. 그것에 대해 조금 더 말할 수 있습니까?
물론-나는 Git (libgit ++, 당신이 원한다면 libgit ++)을위한 C ++ 라이브러리를 작성하고 있었고 이것과 비슷한 문제가 발생했다. 큰 (매우 큰) 파일을 열 수 있고 성능이 총 견이 아니어야했다. (와 마찬가지로 std::fstream
).
Boost::Iostreams
이미 Mapping_file 소스를 가지고 있지만 문제는 mmap
전체 파일 을 핑 하고 있다는 것입니다 . 이로 인해 2 ^ (wordsize)로 제한됩니다. 32 비트 시스템에서 4GB는 충분하지 않습니다. .pack
Git에 파일보다 훨씬 큰 파일 이 있다고 예상하는 것은 무리가 없으므로 일반 파일 I / O에 의존하지 않고 청크로 파일을 읽어야했습니다. 의 덮개에서 Boost::Iostreams
, 나는 사이의 상호 작용의 다소 다른보기 인 소스 구현 std::streambuf
및 std::istream
. 당신은 또한 단지 상속에 의해 유사한 접근 방법을 시도 할 수 std::filebuf
으로 mapped_filebuf
유사, 상속 std::fstream
에를 a mapped_fstream
. 두 사람 사이의 상호 작용은 제대로 이해하기 어렵습니다. Boost::Iostreams
일부 작업이 완료되었으며 필터 및 체인에 대한 후크도 제공하므로 그렇게 구현하는 것이 더 유용 할 것이라고 생각했습니다.
답변
여기에 많은 주요 요점을 다루는 좋은 답변이 이미 많이 있으므로 직접 위에서 언급하지 않은 몇 가지 문제를 추가하겠습니다. 즉,이 답변은 장단점의 포괄적 인 것으로 간주되어서는 안되며 다른 답변에 대한 부록으로 간주되어야합니다.
mmap은 마술처럼 보인다
파일이 이미 완전히 캐시되는 경우 촬영 일을 기준으로 2 , mmap
처럼 거의 보일 수도 마법 :
mmap
전체 파일을 (잠재적으로) 매핑하기 위해 한 번의 시스템 호출 만 있으면됩니다. 그 후에는 더 이상 시스템 호출이 필요하지 않습니다.mmap
커널에서 사용자 공간으로 파일 데이터의 사본이 필요하지 않습니다.mmap
컴파일러 자동 벡터화, SIMD 내장 함수, 프리 페치, 최적화 된 메모리 내 구문 분석 루틴, OpenMP 등과 같이 메모리에 대해 수행 할 수있는 고급 트릭으로 파일을 처리하는 것을 포함하여 파일을 “메모리로”액세스 할 수 있습니다 .
파일이 이미 캐시에있는 경우에는 이길 수없는 것처럼 보입니다. 커널 페이지 캐시에 메모리로 직접 액세스하면 그보다 더 빠를 수 없습니다.
글쎄요
mmap은 실제로 마술이 아닙니다 …
mmap은 여전히 페이지 당 작업을 수행합니다.
기본 숨겨진 비용 mmap
대 vs read(2)
(실제로 비슷한 OS 수준의 시스템 콜입니다) 블록 읽는 )은 mmap
사용자 공간의 모든 4K 페이지에 대해 “일부 작업”을 수행해야한다는 것입니다. 페이지 결함 메커니즘.
예를 들어 mmap
, 전체 파일에 대한 일반적인 구현은 100GB 파일을 읽기 위해 100GB / 4K = 2,500 만 개의 결함이 필요합니다. 자, 이것들은 사소한 결함 이지만 250 억 페이지의 결함은 여전히 빠르지 않을 것입니다. 사소한 결함의 비용은 아마도 가장 좋은 경우 100의 나노에있을 것입니다.
mmap은 TLB 성능에 크게 의존합니다
이제 리턴하기 전에 모든 페이지 테이블을 설정 MAP_POPULATE
하도록 mmap
지시 할 수 있으므로 액세스하는 동안 페이지 결함이 없어야합니다. 자, 이것은 전체 파일을 RAM으로 읽는다는 작은 문제가 있습니다 .100GB 파일을 매핑하려고하면 폭발 할 것입니다. 그러나 지금은 무시하십시오 3 . 커널은 이러한 페이지 테이블을 설정하기 위해 페이지 당 작업 을 수행해야합니다 (커널 시간으로 표시됨). 이는 mmap
접근 방식 에서 주요 비용이되며 파일 크기에 비례합니다 (즉, 파일 크기가 커질수록 상대적으로 덜 중요하지 않음) 4 .
마지막으로, 사용자 공간에서 액세스 할 때조차도 이러한 매핑이 완전히 자유롭지는 않습니다 (파일 기반이 아닌 대용량 메모리 버퍼와 비교 mmap
). 페이지 테이블이 설정되면 새 페이지에 대한 각 액세스는 개념적으로 TLB 미스가 발생합니다. 이후mmap
페이지 캐시와 4K 페이지를 사용하여 파일 수단을 보내고, 다시 100GB의 파일이 비용 25 만 번이 발생.
이제 이러한 TLB 누락의 실제 비용은 최소한 다음 하드웨어 측면에 크게 좌우됩니다. (a) 보유하고있는 4K TLB 수 및 나머지 번역 캐싱 작동 방식 (b) 하드웨어 프리 페치 처리 방식 프리 페치가 페이지 워크를 트리거 할 수 있습니까? (c) 페이지 워킹 하드웨어의 속도와 병렬 속도 최신 하이 엔드 x86 인텔 프로세서에서 페이지 워킹 하드웨어는 일반적으로 매우 강력합니다. 최소 2 개의 병렬 페이지 워커가 있고, 페이지 워킹이 계속 실행되면서 동시에 발생할 수 있으며, 하드웨어 프리 페치가 페이지 워킹을 트리거 할 수 있습니다. 따라서 스트리밍 읽기로드 에 대한 TLB 영향 은 상당히 낮으며 이러한로드는 종종 페이지 크기에 관계없이 비슷하게 수행됩니다. 그러나 다른 하드웨어는 일반적으로 훨씬 나쁩니다!
read ()는 이러한 함정을 피합니다
read()
일반적으로 기초가 무엇 콜, 유형 호출 C, C ++에, 예를 들면 제공되며 다른 언어 모두가 잘 인식 것을 하나의 기본 단점이있다 “블록 읽기”
read()
N 바이트를 호출 할 때마다 N 바이트를 커널에서 사용자 공간으로 복사해야합니다.
반면에, 위의 비용을 대부분 피할 수 있습니다. 2,500 만 개의 4K 페이지를 사용자 공간에 매핑 할 필요가 없습니다. 일반적으로 malloc
사용자 공간에서 단일 버퍼 소형 버퍼를 사용할 수 있으며 모든 read
통화에 반복적으로이를 재사용 할 수 있습니다 . 커널 쪽에서는 4K 페이지 또는 TLB 누락 문제가 거의 없습니다. 모든 RAM은 대개 매우 큰 페이지 (예 : x86의 1GB 페이지)를 사용하여 선형으로 매핑되므로 페이지 캐시의 기본 페이지가 포함됩니다 커널 공간에서 매우 효율적입니다.
따라서 기본적으로 다음과 같은 비교를 통해 큰 파일을 한 번 읽을 때 어느 쪽이 더 빠른지 결정합니다.
이 mmap
방법 을 사용하면 파일 내용을 커널에서 사용자 공간으로 복사하는 바이트 단위 작업보다 비용이 많이 드는 방식 으로 페이지 당 추가 작업이 필요 read()
합니까?
많은 시스템에서 실제로는 거의 균형을 이룹니다. 각각은 완전히 다른 하드웨어 및 OS 스택 속성으로 확장됩니다.
특히 다음과 같은 경우 mmap
접근 방식이 상대적으로 빨라집니다.
- OS는 빠른 사소한 결함 처리, 특히 결함 해결과 같은 사소한 결함 벌크 최적화를 갖추고 있습니다.
- OS는
MAP_POPULATE
예를 들어 기본 페이지가 물리적 메모리에서 연속적인 경우 큰 맵을 효율적으로 처리 할 수 있는 좋은 구현을 가지고 있습니다. - 하드웨어는 큰 TLB, 빠른 2 차 레벨 TLB, 빠른 페이지 병렬 및 병렬 페이지 워커, 번역과의 프리 페치 상호 작용 등과 같은 강력한 페이지 변환 성능을 가지고 있습니다.
… 다음과 같은 경우 read()
접근 방식이 상대적으로 빨라집니다.
read()
콜 좋은 복사 성능을 가지고있다. 예를 들어,copy_to_user
커널 쪽에서 좋은 성능.- 커널은 예를 들어 하드웨어를 지원하는 몇 개의 큰 페이지 만 사용하여 메모리를 매핑하는 효율적인 (사용자 영역에 비해) 방법이 있습니다.
- 커널에는 빠른 syscall과 syscall 전체에서 커널 TLB 항목을 유지하는 방법이 있습니다.
위의 하드웨어 요소는 크게 다릅니다 는 동일한 제품군 (예 : x86 세대 및 특히 시장 세그먼트 내) 및 아키텍처 (예 : ARM vs x86 vs PPC) 내에서도 플랫폼 .
OS 요소도 계속 변하고 있으며, 양쪽의 다양한 개선으로 인해 한 접근 방식 또는 다른 접근 방식의 상대 속도가 크게 향상되었습니다. 최근 목록에는 다음이 포함됩니다.
- 위에 설명 된 장애 해결 기능이 추가되어 실제로
mmap
사례가없는 경우에 도움이됩니다MAP_POPULATE
. - 예를 들어, 빠른 경우를 사용하여 빠른 경로
copy_to_user
방법을 추가 하면 실제로 도움이 됩니다.arch/x86/lib/copy_user_64.S
REP MOVQ
read()
스펙터 및 멜트 다운 후 업데이트
스펙터 및 멜트 다운 취약점에 대한 완화 조치로 시스템 호출 비용이 상당히 증가했습니다. 필자가 측정 한 시스템에서 “아무것도하지 않는”시스템 호출 비용 (통화에 의해 수행 된 실제 작업을 제외하고 시스템 호출의 순수한 오버 헤드 추정값)은 일반적으로 약 100ns에서 약 700ns의 최신 Linux 시스템. 또한 시스템에 따라 Meltdown 전용 페이지 테이블 격리 수정 프로그램은 TLB 항목을 다시로드해야하기 때문에 직접 시스템 호출 비용 외에 추가적인 다운 스트림 영향을 미칠 수 있습니다.
이 모든 것은 다음 read()
과 비교할 때 기반 방법에 대한 상대적 단점입니다mmap
때문에 기반 방법 read()
방법은 각 데이터 “버퍼 크기”가치가 하나의 시스템 호출을 수행한다. 큰 버퍼를 사용하면 일반적으로 L1 크기를 초과하므로 성능이 저하되어 캐시 누락이 계속 발생하므로 버퍼 크기를 임의로 늘려이 비용을 상각 할 수 없습니다.
반면을 사용하면 단일 시스템 호출 비용으로 mmap
대규모 메모리 영역에 매핑 MAP_POPULATE
하고 효율적으로 액세스 할 수 있습니다.
1 여기에는 파일이 완전히 캐싱되지 않았지만 OS 미리 읽기가 충분하기 때문에 파일이 제대로 표시되는 경우도 포함됩니다 (예 : 일반적으로 페이지는 시간에 따라 캐시됩니다) 원하는). 이 방법은 작품을 미리 읽을 수 있기 때문에 자주와 매우 다르다하지만 미묘한 문제 mmap
와 read
통화 및에 설명 된대로 더 “조언”호출에 의해 조정할 수 2 .
2 … 파일이 캐시 되지 않은 경우 액세스 패턴이 기본 하드웨어에 대한 동정심을 포함하여 IO 관련 문제에 의해 행동이 완전히 지배 될 것입니다. 예를 들어 사용 madvise
또는 fadvise
호출 (및 액세스 패턴을 개선하기 위해 수행 할 수있는 응용 프로그램 수준 변경)을 통해 가능합니다.
3 예를 들어 mmap
작은 크기의 창 ( 예 : 100MB) 을 순차적으로 사용하면 문제를 해결할 수 있습니다 .
4 사실, 그것은 판명 MAP_POPULATE
접근 (적어도 하나의 일부 하드웨어 / OS 조합) 만 약간 빠른 아마 커널이 사용하고 있기 때문에, 그것을 사용하지 않는 것보다 faultaround – 사소한 오류의 실제 수는 16의 비율로 감소되도록 정도.
답변
벤 콜린스가 슬라이딩 윈도우 mmap 소스 코드를 잃어버린 것이 유감입니다. Boost에 있으면 좋을 것 같습니다.
예, 파일 매핑이 훨씬 빠릅니다. 본질적으로 OS 가상 메모리 하위 시스템을 사용하여 메모리를 디스크에 연결하거나 그 반대로 연결합니다. OS 커널 개발자가 더 빠르게 만들 수 있다면 이런 식으로 생각하십시오. 데이터베이스, 부팅 시간, 프로그램로드 시간 등 모든 것이 더 빨라지기 때문입니다.
여러 개의 연속 페이지를 한 번에 매핑 할 수 있기 때문에 슬라이딩 윈도우 방식은 그리 어렵지 않습니다. 따라서 단일 레코드 중 가장 큰 레코드가 메모리에 들어가는 한 레코드 크기는 중요하지 않습니다. 중요한 것은 부기 관리입니다.
getpagesize () 경계에서 레코드가 시작되지 않으면 이전 페이지에서 맵핑을 시작해야합니다. 맵핑 된 영역의 길이는 레코드의 첫 번째 바이트 (필요한 경우 getpagesize ()의 가장 가까운 배수로 내림)에서 레코드의 마지막 바이트 (getpagesize ()의 가장 가까운 배수로 반올림)까지 확장됩니다. 레코드 처리가 끝나면 레코드를 unmap ()하고 다음으로 이동할 수 있습니다.
이 모든 것은 Windows에서 CreateFileMapping () 및 MapViewOfFile () (및 GetSystemInfo ()을 사용하여 SYSTEM_INFO.dwAllocationGranularity — SYSTEM_INFO.dwPageSize가 아님)을 사용하여 제대로 작동합니다.
답변
mmap는 더 빠를 것이지만, 나는 얼마나 몰라요. 그것은 당신의 코드에 달려 있습니다. mmap을 사용하는 경우 전체 파일을 한 번에 mmap하는 것이 가장 좋으므로 인생을 훨씬 쉽게 만들 수 있습니다. 잠재적 인 문제 중 하나는 파일이 4GB보다 큰 경우 (또는 실제로 한계가 2GB 이하인 경우) 64 비트 아키텍처가 필요하다는 것입니다. 따라서 32 환경을 사용하는 경우에는 사용하고 싶지 않을 것입니다.
그러나 성능 향상을위한 더 나은 경로가있을 수 있습니다. 입력 파일이 여러 번 스캔 된다고 말 했는데 한 번 에 읽은 다음 완료하면 훨씬 빠를 수 있습니다.
답변
파일을 사전 처리해야 할 수도 있으므로 각 레코드는 별도의 파일에 있어야합니다 (또는 각 파일이 mmap 가능 크기 여야 함).
또한 다음 레코드로 이동하기 전에 각 레코드에 대한 모든 처리 단계를 수행 할 수 있습니까? 어쩌면 IO 오버 헤드를 피할 수 있습니까?