mmap () 대 판독 블록


185

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

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

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

실제로 전체 구현을 작성하지 않고이 두 옵션 중에서 어떻게 결정할 수 있습니까? 경험적 규칙 (예 : mmap()2 배 빠름) 또는 간단한 테스트?


1
이것은 흥미로운 내용입니다 : medium.com/@sasha_f/… 실험에서 mmap()syscall을 사용하는 것보다 2-6 배 빠릅니다 (예 :)read() .
mplattner

답변:


208

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 결과의 최상위에 계속 올라 왔습니다.)


오늘날 테스트하지 않고 2000 년대의 하드웨어 및 소프트웨어를 기반으로 한 조언을 사용하는 것은 매우 의심스러운 접근 방법입니다. 또한 그 스레드에서 mmapvs read()에 대한 많은 사실은 과거와 마찬가지로 여전히 사실이지만 전반적인 성능은 장단점을 추가하여 특정 하드웨어 구성을 테스트하여 만 결정할 수는 없습니다. 예를 들어, "mmap에 대한 호출이 읽기보다 오버 헤드가 더 많다"는 것은 논쟁의 여지가 있습니다. 예 mmap는 프로세스 페이지 테이블에 맵핑을 추가해야하지만 read모든 읽기 바이트를 커널에서 사용자 공간으로 복사해야합니다.
BeeOnRope 2:26에

결론은 (2018 년경 현대 인텔) 하드웨어에서 페이지 크기보다 큰 (4 KiB) 읽기보다 mmap오버 헤드가 적다는 것 read입니다. 이제 데이터를 드물게 무작위로 액세스하려는 경우 mmap실제로 실제로는 훌륭하지만 대화는 필요하지 않습니다 mmap. 순차 액세스에도 여전히 최고 일 수 있습니다.
BeeOnRope

1
@BeeOnRope : 2000 년대의 하드웨어와 소프트웨어에 근거한 조언에 회의적 일 수 있지만, 방법론과 데이터를 제공하지 않는 벤치 마크에 대해서는 더 회의적입니다. mmap더 빠른 사례를 원한다면 표로 작성된 결과와 프로세서 모델 번호가있는 전체 테스트 장치 (소스 코드)를 최소한으로 볼 것으로 예상됩니다.
Dietrich Epp

@BeeOnRope : 또한 이와 같은 메모리 시스템의 비트를 테스트 할 때 TLB 플러시가 나머지 프로그램의 성능에 부정적인 영향을 미치기 때문에 마이크로 벤치 마크가 매우 기만적 일 수 있으며이 영향은 나타나지 않습니다. mmap 자체 만 측정합니다.
Dietrich Epp

2
@ DietrichEpp-예, TLB 효과에 정통합니다. 참고 mmap특별한 상황 (만 제외하고 TLB를 플러시하지 않는 munmap힘). 내 테스트에는 마이크로 벤치 마크 (포함 munmap) 실제 사용 사례에서 실행중인 "애플리케이션"이 모두 포함되었습니다 . 물론 내 응용 프로그램은 응용 프로그램과 같지 않으므로 사람들은 로컬에서 테스트해야합니다. mmap마이크로 벤치 마크가 선호 하는 것은 확실하지 않습니다 read(). 사용자 측 대상 버퍼가 일반적으로 L1에 머무르기 때문에 크게 향상됩니다. 이는 더 큰 응용 프로그램에서는 발생하지 않을 수 있습니다. 예, "복잡합니다".
BeeOnRope 3:26에

47

주요 성능 비용은 디스크 I / O입니다. "mmap ()"은 확실히 istream보다 빠르지 만 디스크 i / o가 런타임을 지배하므로 차이가 눈에 띄지 않을 수 있습니다.

나는 (아래 / 위 참조) "의 mmap ()는 것을 자신의 주장을 테스트하는 벤 콜린스의 코드 조각을 시도 방법은 빨리"와 측정 가능한 차이를 찾을 수 없습니다. 그의 답변에 대한 내 의견을 참조하십시오.

나는 확실히 것 없는 당신의 "기록"거대한하지 않는 한 별도로 차례로 각 레코드를 mmap 할 추천 - 그 끔찍하게 느린 것, 각 레코드에 대해이 시스템 호출을 필요로하고 가능한 디스크 메모리 캐시에서 페이지를 잃고 .... .

귀하의 경우 mmap (), istream 및 저수준 open () / read () 호출은 모두 거의 동일하다고 생각합니다. 이 경우 mmap ()을 권장합니다.

  1. 파일 내에 무작위 액세스 (순차 아님)가 있으며
  2. 모든 것이 메모리에 편안하게 맞거나 파일 내에 참조 위치가 있으므로 특정 페이지를 매핑하고 다른 페이지를 매핑 할 수 있습니다. 그렇게하면 운영 체제는 사용 가능한 RAM을 사용하여 최대의 이점을 얻습니다.
  3. 또는 여러 프로세스가 동일한 파일에서 읽고 작동하는 경우 프로세스가 모두 동일한 실제 페이지를 공유하므로 mmap ()은 환상적입니다.

(btw-mmap () / MapViewOfFile ()을 좋아합니다).


랜덤 액세스에 대한 좋은 지적 : 이것은 내 인식을 이끌어내는 것 중 하나 일 수 있습니다.
벤 콜린스

1
파일이 메모리에 편안하게 맞고 주소 공간에만 적합해야한다고 말하지는 않습니다. 따라서 64 비트 시스템에서는 큰 파일을 매핑하지 않을 이유가 없습니다. OS는이를 처리하는 방법을 알고 있습니다. 스와핑에 사용 된 것과 동일한 논리이지만이 경우 디스크에 추가 스왑 공간이 필요하지 않습니다.
MvG

@MvG : 디스크 I / O에 대한 요점을 이해하십니까? 파일이 주소 공간에 적합하지만 메모리에는 액세스하지 않고 임의 액세스 권한이있는 경우 디스크 헤드 이동 및 탐색을 요구하는 모든 레코드 액세스 또는 SSD 페이지 작업을 수행 할 수 있으며, 이는 성능 저하의 원인이됩니다.
Tim Cooper

3
디스크 입출력 측면은 액세스 방법과 독립적이어야합니다. RAM보다 큰 파일에 실제로 무작위로 액세스 할 수있는 경우 mmap과 seek + read는 모두 디스크 바인딩이 심합니다. 그렇지 않으면 두 가지 모두 캐시를 활용할 수 있습니다. 메모리 크기와 비교할 때 파일 크기를 어느 방향 으로든 강력한 인수로 보지 않습니다. 반면에 파일 크기와 주소 공간은 특히 임의 액세스에 대한 강력한 논거입니다.
MvG

내 원래의 대답은 "모든 것이 메모리에 편안하게 맞거나 파일 내에 참조의 근거가 있습니다"라는 점을 가지고 있습니다. 그래서 두 번째 요점은 당신이 말하는 것을 다룹니다.
Tim Cooper

43

의 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 일부 작업이 완료되었으며 필터 및 체인에 대한 후크도 제공하므로 그렇게 구현하는 것이 더 유용 할 것이라고 생각했습니다.


3
RE : Windows에서 mmaped 파일 캐시. 정확히 : 파일 버퍼링이 활성화되면 커널 메모리는 읽고있는 파일을 내부적으로 매핑하고 해당 버퍼를 읽고 프로세스에 다시 복사합니다. 추가 복사 단계를 제외하고 메모리가 직접 매핑 한 것처럼 보입니다.
Chris Smith

6
나는 받아 들여진 대답에 동의하지 않는 것을 싫어하지만이 대답이 틀렸다고 생각합니다. 64 비트 Linux 시스템에서 제안을 따르고 코드를 시도했으며 mmap ()은 STL 구현보다 빠르지 않았습니다. 또한 이론적으로 'mmap ()'이 더 빠를 것이라고는 기대하지 않습니다.
Tim Cooper

3
@Tim Cooper : 관심 이있는 스레드 ( markmail.org/message/… )를 찾을 수 있습니다 . mmap은 Linux에서 올바르게 최적화되지 않았으며 최상의 결과를 얻으려면 테스트에서 madvise를 사용해야합니다.
Ben Collins

9
벤에게 : 그 링크를 읽었습니다. Linux에서 'mmap ()'이 더 빠르지 않고 Windows에서 MapViewOfFile ()이 더 빠르지 않다면 "mmap이 더 빠르다"고 주장 할 수 있습니까? 또한 이론적 인 이유로 mmap ()은 순차적 읽기에서 더 빠르지 않다고 생각합니다. 반대로 설명이 있습니까?
Tim Cooper

11
벤, 왜 mmap()한 번에 한 페이지 씩 파일 을 작성 해야합니까? a size_t가 파일 크기 (64 비트 시스템 일 가능성이 높음)를 보유 할만큼 충분한 용량이면 한 mmap()번의 호출로 전체 파일 만 가능 합니다.
Steve Emmerson

39

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

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의 비율로 감소되도록 정도.


4
이 복잡한 문제에 대한보다 미묘한 답변을 제공해 주셔서 감사합니다. 대부분의 사람들에게는 mmap이 더 빠르다는 것이 명백해 보이지만 실제로는 그렇지 않습니다. 내 실험에서, 메모리 내 인덱스를 사용하여 100GB의 큰 데이터베이스에 무작위로 액세스하는 것은 pread ()를 사용하여 더 빠르다는 것이 밝혀졌습니다. 그리고 그것은 업계의 많은 사람들 이 같은 것을 관찰 한 것 같습니다 .
Caetano Sauer

5
예, 시나리오에 따라 다릅니다. 읽기가 충분히 작고 시간이 지남에 따라 동일한 바이트를 반복적으로 읽는 경향이 mmap있다면 고정 커널 호출 오버 헤드를 피할 수 있기 때문에 극복 할 수없는 이점이 있습니다. 반면에, mmapTLB 압력도 증가하고 현재 프로세스에서 바이트를 처음 읽은 "warm up"단계 (실제로는 페이지 페이지에 있음에도 불구하고)에 대해 실제로 느려집니다. read예를 들어 인접한 페이지를 "고장난"하는 것보다 더 많은 작업을 수행 할 수 있습니다. 동일한 응용 프로그램의 경우 "웜업"이 가장 중요합니다! @CaetanoSauer
BeeOnRope

"...하지만 250 억 페이지 오류는 여전히 빠르지 않을 것입니다 ..."라고 말해야합니다. "...하지만 2500 페이지 오류는 여전히 빠르지 않을 것입니다 ..." . 나는 100 % 긍정적이지 않기 때문에 직접 편집하지 않는 이유입니다.
Ton van den Heuvel

7

벤 콜린스가 슬라이딩 윈도우 mmap 소스 코드를 잃어버린 것이 유감입니다. Boost에 있으면 좋을 것 같습니다.

예, 파일 매핑이 훨씬 빠릅니다. 본질적으로 OS 가상 메모리 하위 시스템을 사용하여 메모리를 디스크에 연결하거나 그 반대로 연결합니다. OS 커널 개발자가 더 빠르게 만들 수 있다면 이런 식으로 생각하십시오. 데이터베이스, 부팅 시간, 프로그램로드 시간 등 모든 것이 더 빨라지기 때문입니다.

여러 개의 연속 페이지를 한 번에 매핑 할 수 있기 때문에 슬라이딩 윈도우 방식은 그리 어렵지 않습니다. 따라서 단일 레코드 중 가장 큰 레코드가 메모리에 들어가는 한 레코드 크기는 중요하지 않습니다. 중요한 것은 부기 관리입니다.

getpagesize () 경계에서 레코드가 시작되지 않으면 이전 페이지에서 맵핑을 시작해야합니다. 맵핑 된 영역의 길이는 레코드의 첫 번째 바이트 (필요한 경우 getpagesize ()의 가장 가까운 배수로 내림)에서 레코드의 마지막 바이트 (getpagesize ()의 가장 가까운 배수로 반올림)까지 확장됩니다. 레코드 처리가 끝나면 레코드를 unmap ()하고 다음으로 이동할 수 있습니다.

이 모든 것은 Windows에서 CreateFileMapping () 및 MapViewOfFile () (및 GetSystemInfo ()을 사용하여 SYSTEM_INFO.dwAllocationGranularity --- SYSTEM_INFO.dwPageSize가 아님)을 사용하여 제대로 작동합니다.


방금 googled하고 dwAllocationGranularity에 대한이 작은 발췌 문장을 발견했습니다 .dwPageSize를 사용하고 있었고 모든 것이 깨졌습니다. 감사!
wickedchicken

4

mmap는 더 빠를 것이지만, 나는 얼마나 몰라요. 그것은 당신의 코드에 달려 있습니다. mmap을 사용하는 경우 전체 파일을 한 번에 mmap하는 것이 가장 좋으므로 인생을 훨씬 쉽게 만들 수 있습니다. 잠재적 인 문제 중 하나는 파일이 4GB보다 큰 경우 (또는 실제로 한계가 2GB 이하인 경우) 64 비트 아키텍처가 필요하다는 것입니다. 따라서 32 환경을 사용하는 경우에는 사용하고 싶지 않을 것입니다.

그러나 성능 향상을위한 더 나은 경로가있을 수 있습니다. 입력 파일이 여러 번 스캔 된다고 말 했는데 한 번 에 읽은 다음 완료하면 훨씬 빠를 수 있습니다.


3

파일을 사전 처리해야 할 수도 있으므로 각 레코드는 별도의 파일에 있어야합니다 (또는 각 파일이 mmap 가능 크기 여야 함).

또한 다음 레코드로 이동하기 전에 각 레코드에 대한 모든 처리 단계를 수행 할 수 있습니까? 어쩌면 IO 오버 헤드를 피할 수 있습니까?


3

mmap'd 파일 I / O가 더 빨라질 것이라는 데 동의하지만 코드를 벤치마킹하는 동안 카운터 예제가 다소 최적화 되어서는 안 됩니까?

벤 콜린스는 다음과 같이 썼다.

char data[0x1000];
std::ifstream in("file.bin");

while (in)
{
    in.read(data, 0x1000);
    // do something with data 
}

나는 또한 시도하는 것이 좋습니다 :

char data[0x1000];
std::ifstream iifle( "file.bin");
std::istream  in( ifile.rdbuf() );

while( in )
{
    in.read( data, 0x1000);
    // do something with data
}

그리고 그 이상으로, 0x1000이 컴퓨터의 가상 메모리의 한 페이지 크기가 아닌 경우 버퍼 크기를 한 페이지의 가상 메모리와 동일한 크기로 만들 수도 있습니다 ... IMHO mmap'd file I / O still 이기는하지만 이것은 더 가까이해야합니다.


2

내 생각에 mmap () "just"를 사용하면 개발자가 자체 캐싱 코드를 작성하지 않아도됩니다. 간단한 "파일을 한 번에 한 번에 한 번에 읽습니다"의 경우에는 이것이 어렵지 않습니다 (mlbrock이 메모리 사본을 프로세스 공간에 여전히 저장한다고 지적하더라도) 또는 파일에서 앞뒤로 이동하는 경우 비트를 건너 뛰는 등 커널 개발자는 아마도 캐싱 구현보다 더 나은 작업을 수행 했을 것입니다 ...


1
페이지 크기의 청크에서 매우 맹목적으로 작동하는 커널보다 응용 프로그램 별 데이터 캐싱 작업을 더 잘 수행 할 수 있습니다 (예 : 간단한 의사 LRU 체계 만 사용하여 제거 할 페이지 결정). )-올바른 캐싱 세분성에 대해 많이 알고 향후 액세스 패턴에 대한 좋은 아이디어가있을 수 있습니다. mmap캐싱 의 실질적인 이점 은 이미 존재하는 기존 페이지 캐시를 재사용 하기 때문에 해당 메모리를 무료로 확보 할 수 있으며 프로세스간에 공유 할 수 있다는 것입니다.
BeeOnRope 2016 년

2

몇 년 전에 트리 구조를 포함하는 거대한 파일을 메모리에 매핑하는 것을 기억합니다. 트리 노드 할당 및 포인터 설정과 같이 메모리에서 많은 작업이 필요한 일반 직렬화 해제와 비교할 때 속도에 놀랐습니다. 실제로 나는 mmap (또는 Windows의 대응)에 대한 단일 호출을 운영자 신규 및 생성자 호출에 대한 많은 (MANY) 호출과 비교했습니다. 이러한 종류의 작업의 경우 직렬화 해제에 비해 mmap이 뛰어납니다. 물론 하나는 이것에 대한 변동 가능한 포인터를 향상시켜야합니다.


그것은 재난의 요리법처럼 들립니다. 객체 레이아웃이 변경되면 어떻게합니까? 가상 기능이있는 경우 모든 vftbl 포인터가 잘못되었을 수 있습니다. 파일이 매핑되는 위치를 어떻게 제어합니까? 주소를 줄 수는 있지만 힌트 일 뿐이며 커널은 다른 기본 주소를 선택할 수 있습니다.
Jens

안정적이고 명확하게 정의 된 트리 레이아웃이있을 때 완벽하게 작동합니다. 그런 다음 매번 "mmap start address"의 오프셋을 추가하여 모든 것을 관련 구조체에 캐스트하고 내부 파일 포인터를 따를 수 있습니다. 이것은 inode와 디렉토리 트리를 사용하는 파일 시스템과 매우 유사합니다.
Mike76

1

이것은 멀티 스레딩을위한 좋은 유스 케이스처럼 들립니다 ... 하나의 스레드가 데이터를 읽도록 설정하고 다른 스레드는 처리하도록 꽤 쉽게 설정할 수 있다고 생각합니다. 이는 인식 된 성능을 크게 향상시키는 방법 일 수 있습니다. 그냥 생각이야


네. 나는 그것에 대해 생각하고 아마도 나중에 릴리스에서 시도 할 것입니다. 내가 보유한 유일한 예약은 처리가 I / O 대기 시간보다 훨씬 짧아서 큰 이점이 없을 수 있다는 것입니다.
jbl

1

mmap의 가장 큰 장점은 다음과 같은 비동기식 읽기 가능성입니다.

    addr1 = NULL;
    while( size_left > 0 ) {
        r = min(MMAP_SIZE, size_left);
        addr2 = mmap(NULL, r,
            PROT_READ, MAP_FLAGS,
            0, pos);
        if (addr1 != NULL)
        {
            /* process mmap from prev cycle */
            feed_data(ctx, addr1, MMAP_SIZE);
            munmap(addr1, MMAP_SIZE);
        }
        addr1 = addr2;
        size_left -= r;
        pos += r;
    }
    feed_data(ctx, addr1, r);
    munmap(addr1, r);

문제는이 메모리를 최대한 빨리 파일에서 동기화해야한다는 힌트를 줄 올바른 MAP_FLAGS를 찾을 수 없다는 것입니다. MAP_POPULATE가 mmap에 대한 올바른 힌트를 제공하기를 바랍니다 (즉, 호출에서 돌아 오기 전에 모든 내용을로드하려고 시도하지 않지만 feed_data와 함께 비동기 적으로 수행). 적어도 수동으로 2.6.23 이후 MAP_PRIVATE가 없으면 아무것도하지 않는다고 해도이 플래그로 더 나은 결과를 제공합니다.


2
당신이 원하는 posix_madviseWILLNEED 미리 채울에 게으른 힌트를 플래그.
ShadowRanger

@ShadowRanger, 합리적으로 들립니다. 비록 posix_madvise비동기 호출 임을 명확하게 나타 내기 위해 매뉴얼 페이지를 업데이트했지만 . 또한 mlock페이지 결함없이 전체 메모리 영역을 사용할 수있을 때까지 기다리는 사람들 을 참조 하는 것이 좋습니다 .
ony
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.