태그 보관물: mmap

mmap

mmap () 대 판독 블록 파일에는 가변 길이

잠재적으로 100GB 이상 크기의 파일을 처리하는 프로그램을 개발 중입니다. 파일에는 가변 길이 레코드 세트가 포함됩니다. 첫 번째 구현을 시작하여 실행 중이며 특히 입력 파일이 여러 번 스캔되므로 I / O를보다 효율적으로 수행하는 데 성능을 향상시키는 방향으로 찾고 있습니다.

mmap()C ++의 fstream라이브러리 를 통해 블록 을 사용하거나 읽는 것에 대한 경험 규칙이 있습니까? 내가하고 싶은 것은 디스크에서 버퍼로 큰 블록을 읽고 버퍼에서 완전한 레코드를 처리 한 다음 더 읽으십시오.

mmap()코드는 잠재적으로 매우부터 지저분한 얻을 수있는 mmapD 블록은 페이지 경계 (내 이해) 크기에 거짓말에 필요한 ‘및 기록 할 수 잠재적에서 같은 페이지 경계. fstreams를 사용하면 페이지 크기 경계에있는 블록을 읽는 것에 만 국한되지 않기 때문에 레코드의 시작을 찾고 다시 읽을 수 있습니다.

실제로 전체 구현을 작성하지 않고이 두 옵션 중에서 어떻게 결정할 수 있습니까? 경험적 규칙 (예 : 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 ()을 권장합니다.

  1. 파일 내에 무작위 액세스 (순차 아님)가 있으며
  2. 모든 것이 메모리에 편안하게 맞거나 파일 내에 참조 위치가 있으므로 특정 페이지를 매핑하고 다른 페이지를 매핑 할 수 있습니다. 그렇게하면 운영 체제는 사용 가능한 RAM을 사용하여 최대의 이점을 얻습니다.
  3. 또는 여러 프로세스가 동일한 파일에서 읽고 작동하는 경우 프로세스가 모두 동일한 실제 페이지를 공유하므로 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는 충분하지 않습니다. .packGit에 파일보다 훨씬 큰 파일 이 있다고 예상하는 것은 무리가 없으므로 일반 파일 I / O에 의존하지 않고 청크로 파일을 읽어야했습니다. 의 덮개에서 Boost::Iostreams, 나는 사이의 상호 작용의 다소 다른보기 인 소스 구현 std::streambufstd::istream. 당신은 또한 단지 상속에 의해 유사한 접근 방법을 시도 할 수 std::filebuf으로 mapped_filebuf유사, 상속 std::fstream에를 a mapped_fstream. 두 사람 사이의 상호 작용은 제대로 이해하기 어렵습니다. Boost::Iostreams 일부 작업이 완료되었으며 필터 및 체인에 대한 후크도 제공하므로 그렇게 구현하는 것이 더 유용 할 것이라고 생각했습니다.


답변

여기에 많은 주요 요점을 다루는 좋은 답변이 이미 많이 있으므로 직접 위에서 언급하지 않은 몇 가지 문제를 추가하겠습니다. 즉,이 답변은 장단점의 포괄적 인 것으로 간주되어서는 안되며 다른 답변에 대한 부록으로 간주되어야합니다.

mmap은 마술처럼 보인다

파일이 이미 완전히 캐시되는 경우 촬영 일을 기준으로 2 , mmap처럼 거의 보일 수도 마법 :

  1. mmap 전체 파일을 (잠재적으로) 매핑하기 위해 한 번의 시스템 호출 만 있으면됩니다. 그 후에는 더 이상 시스템 호출이 필요하지 않습니다.
  2. mmap 커널에서 사용자 공간으로 파일 데이터의 사본이 필요하지 않습니다.
  3. 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.SREP MOVQread()

스펙터 및 멜트 다운 후 업데이트

스펙터 및 멜트 다운 취약점에 대한 완화 조치로 시스템 호출 비용이 상당히 증가했습니다. 필자가 측정 한 시스템에서 “아무것도하지 않는”시스템 호출 비용 (통화에 의해 수행 된 실제 작업을 제외하고 시스템 호출의 순수한 오버 헤드 추정값)은 일반적으로 약 100ns에서 약 700ns의 최신 Linux 시스템. 또한 시스템에 따라 Meltdown 전용 페이지 테이블 격리 수정 프로그램은 TLB 항목을 다시로드해야하기 때문에 직접 시스템 호출 비용 외에 추가적인 다운 스트림 영향을 미칠 수 있습니다.

이 모든 것은 다음 read()과 비교할 때 기반 방법에 대한 상대적 단점입니다mmap 때문에 기반 방법 read()방법은 각 데이터 “버퍼 크기”가치가 하나의 시스템 호출을 수행한다. 큰 버퍼를 사용하면 일반적으로 L1 크기를 초과하므로 성능이 저하되어 캐시 누락이 계속 발생하므로 버퍼 크기를 임의로 늘려이 비용을 상각 할 수 없습니다.

반면을 사용하면 단일 시스템 호출 비용으로 mmap대규모 메모리 영역에 매핑 MAP_POPULATE하고 효율적으로 액세스 할 수 있습니다.


1 여기에는 파일이 완전히 캐싱되지 않았지만 OS 미리 읽기가 충분하기 때문에 파일이 제대로 표시되는 경우도 포함됩니다 (예 : 일반적으로 페이지는 시간에 따라 캐시됩니다) 원하는). 이 방법은 작품을 미리 읽을 수 있기 때문에 자주와 매우 다르다하지만 미묘한 문제 mmapread통화 및에 설명 된대로 더 “조언”호출에 의해 조정할 수 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 오버 헤드를 피할 수 있습니까?