메뉴 닫기

WinAPI 메모리 맵 파일로 구현하는 빠른 파일 I/O와 데이터 공유

⚡ WinAPI 메모리 맵 파일로 구현하는 빠른 파일 I/O와 데이터 공유

🚀 파일을 메모리에 매핑해 속도를 높이고 프로세스 간 데이터를 손쉽게 주고받는 방법

대용량 파일을 처리하거나 여러 프로세스가 동일한 데이터를 공유해야 하는 상황에서는 전통적인 파일 읽기·쓰기 방식이 성능의 한계를 보일 때가 많습니다.
이럴 때 메모리 맵 파일(Memory-Mapped File)을 사용하면, 운영체제가 파일을 메모리에 직접 매핑하여 디스크 I/O를 최소화하고 매우 빠른 접근이 가능합니다.
윈도우 환경에서는 WinAPI를 통해 간단히 구현할 수 있으며, 특히 이미지, 로그, 대규모 데이터셋 처리나 IPC(프로세스 간 통신)에 탁월한 효율을 발휘합니다.
이 글에서는 메모리 맵 파일의 기본 개념부터 실무 적용 방법까지 쉽게 풀어 설명해 드리겠습니다.

단순히 파일을 읽고 쓰는 것을 넘어, 메모리에 매핑된 데이터를 여러 프로세스가 동시에 활용할 수 있다는 점은 매우 강력합니다.
뿐만 아니라, 파일 크기가 커도 필요한 부분만 메모리에 로드하는 방식 덕분에 메모리 사용량도 효율적으로 관리할 수 있습니다.
아래에서 구조, 동작 원리, WinAPI 예제 코드, 그리고 실무에서의 활용 사례까지 단계별로 살펴보겠습니다.



💡 메모리 맵 파일이란?

메모리 맵 파일(Memory-Mapped File)은 운영체제가 파일의 내용을 메모리에 직접 매핑하여, 디스크 접근 없이도 데이터를 읽고 쓸 수 있게 해주는 기능입니다.
이 방식은 파일의 특정 부분을 메모리처럼 다룰 수 있어, 기존의 ReadFile이나 WriteFile API보다 훨씬 빠른 입출력을 제공합니다.
또한 동일한 매핑 객체를 여러 프로세스에서 열면, 복사 없이 데이터를 공유할 수 있어 IPC(프로세스 간 통신)에서도 자주 활용됩니다.

윈도우에서는 CreateFileMappingMapViewOfFile 함수를 사용하여 메모리 매핑을 구현합니다.
이때 파일이 실제로 전부 메모리에 로드되는 것은 아니며, 필요한 페이지가 접근될 때마다 운영체제가 자동으로 로드하는 방식(지연 로딩)을 사용합니다.
덕분에 파일이 매우 크더라도 메모리 사용량을 효율적으로 관리할 수 있습니다.

📌 주요 특징

  • 디스크 I/O 최소화로 인한 고속 데이터 접근
  • 🔗여러 프로세스 간 데이터 공유 가능
  • 📂대용량 파일도 부분 매핑으로 메모리 절약
  • 🛠️WinAPI의 CreateFileMappingMapViewOfFile로 구현

📌 기본 동작 원리

1. 먼저 파일을 열고, CreateFileMapping을 호출하여 매핑 객체를 생성합니다.
2. MapViewOfFile을 사용하여 해당 매핑 객체를 프로세스의 가상 메모리 주소 공간에 연결합니다.
3. 매핑된 주소를 통해 데이터를 읽거나 쓰면, 운영체제가 자동으로 파일의 해당 부분을 디스크에서 메모리로 가져오거나 반대로 디스크에 기록합니다.

CODE BLOCK
HANDLE hFile = CreateFile(L"data.bin", GENERIC_READ | GENERIC_WRITE, 
    0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

LPVOID pView = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);

// 메모리에 매핑된 데이터 사용
strcpy((char*)pView, "Hello Memory Mapped File!");

UnmapViewOfFile(pView);
CloseHandle(hMap);
CloseHandle(hFile);

⚙️ WinAPI에서의 메모리 매핑 방식

윈도우에서 메모리 맵 파일을 구현하려면 크게 두 가지 방식이 있습니다.
첫째는 디스크 기반 매핑이고, 둘째는 페이지 파일 기반 매핑입니다.
디스크 기반 매핑은 실제 파일과 연결되어 데이터가 지속되며, 페이지 파일 기반 매핑은 물리적인 파일 없이도 RAM 상에서만 데이터를 공유할 수 있습니다.
이 차이를 이해하면, 상황에 맞는 최적의 매핑 방식을 선택할 수 있습니다.

📌 디스크 기반 매핑

디스크 기반 매핑은 CreateFile로 파일을 연 뒤, CreateFileMapping을 호출해 해당 파일을 메모리에 매핑합니다.
이 방식은 파일이 실제 디스크에 존재하므로, 프로그램 종료 후에도 데이터가 유지됩니다.
대용량 로그 파일 처리나 미디어 파일 접근에 적합합니다.

📌 페이지 파일 기반 매핑

페이지 파일 기반 매핑은 CreateFileMapping 호출 시 파일 핸들을 INVALID_HANDLE_VALUE로 지정하여 생성합니다.
이는 실제 디스크 파일 없이 운영체제의 가상 메모리 공간을 활용하는 방식으로, 주로 IPC나 임시 데이터 저장소로 사용됩니다.
프로그램이 종료되면 데이터는 사라집니다.

📌 구현 흐름

단계 설명
1 CreateFile 또는 INVALID_HANDLE_VALUE로 매핑 준비
2 CreateFileMapping으로 매핑 객체 생성
3 MapViewOfFile로 프로세스 메모리에 매핑
4 데이터 읽기/쓰기 수행
5 UnmapViewOfFile과 CloseHandle로 리소스 해제

💡 TIP: 디스크 기반 매핑은 데이터 영속성이 필요할 때, 페이지 파일 기반 매핑은 속도와 공유가 중요할 때 선택하는 것이 좋습니다.



🚀 빠른 I/O 구현 방법

메모리 맵 파일은 파일을 메모리에 직접 매핑해 커널 페이지 캐시를 통해 접근하므로, 일반적인 ReadFile 및 WriteFile 호출보다 캐시 적중 시 지연이 훨씬 낮습니다.
효율을 극대화하려면 적절한 접근 권한과 뷰 크기, 정렬, 플러시 전략을 세밀하게 조정해야 합니다.
또한 시스템 페이지 크기와 할당 단위(Allocation Granularity)에 맞춰 오프셋을 정렬하면 페이지 폴트 수를 줄이고, 필요 범위만 부분 매핑하여 메모리 압력을 낮출 수 있습니다.

⚡ 성능을 위한 핵심 파라미터

1) 보호 플래그와 접근 권한을 최소화합니다.
읽기 전용이라면 PAGE_READONLY와 FILE_MAP_READ 조합이 불필요한 쓰기 권한 대비 더 안전하고 가볍습니다.
2) 큰 파일은 전체 매핑보다 슬라이딩 윈도우(부분 뷰)로 나눠 매핑합니다.
3) 쓰기 시에는 FlushViewOfFile과 FlushFileBuffers를 적절히 호출해 내구성을 보장하되, 지나친 플러시는 역효과입니다.
4) 순차 접근과 랜덤 접근 패턴을 구분하여 뷰 크기와 접근 단위를 조정하면 TLB 및 캐시 효율을 높일 수 있습니다.

🧩 기본 구현 예시

CODE BLOCK
// 빠른 읽기/쓰기용 메모리 맵 파일 (부분 매핑, 슬라이딩 윈도우)
SYSTEM_INFO si = {0};
GetSystemInfo(&si); // si.dwAllocationGranularity 확인

HANDLE hFile = CreateFileW(L"bigdata.bin",
    GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
    NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

LARGE_INTEGER fileSize = {0};
GetFileSizeEx(hFile, &fileSize);

// 매핑 객체 생성 (읽기/쓰기)
HANDLE hMap = CreateFileMappingW(hFile, NULL, PAGE_READWRITE,
    0, 0, NULL);

// 오프셋은 Allocation Granularity 배수로 정렬
SIZE_T viewSize = 64 * 1024 * 1024; // 64MB 창
ULONGLONG offset = 0;

while (offset < (ULONGLONG)fileSize.QuadPart) {
    ULONGLONG aligned = (offset / si.dwAllocationGranularity) * si.dwAllocationGranularity;
    SIZE_T delta = (SIZE_T)(offset - aligned);
    SIZE_T thisView = min(viewSize + delta, (SIZE_T)((ULONGLONG)fileSize.QuadPart - aligned));

    DWORD offHigh = (DWORD)((aligned >> 32) & 0xFFFFFFFF);
    DWORD offLow  = (DWORD)(aligned & 0xFFFFFFFF);

    LPVOID pView = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, offHigh, offLow, thisView);

    BYTE* p = (BYTE*)pView + delta;
    // 예: p부터 length 바이트 처리 (읽기/쓰기)
    // memcpy(p, src, length); // 쓰기 예시

    // 필요 시 디스크에 플러시 (너무 자주 호출하지 않기)
    // FlushViewOfFile(p, length);

    UnmapViewOfFile(pView);
    offset += viewSize;
}

FlushFileBuffers(hFile);
CloseHandle(hMap);
CloseHandle(hFile);

🛠️ 체크리스트

  • 🧭GetSystemInfo로 페이지 크기·할당 단위 확인 후 오프셋 정렬
  • 🔒필요 권한만 부여: PAGE_READONLY + FILE_MAP_READ 또는 PAGE_READWRITE + FILE_MAP_WRITE
  • 🧰대용량은 전체 매핑 대신 부분 뷰로 슬라이딩 처리
  • 💾내구성 필요 시 FlushViewOfFile → FlushFileBuffers 순으로 플러시
  • 📐랜덤/순차 접근 패턴에 맞춰 뷰 크기와 처리 단위 조정

⚠️ 주의: 파일 크기 변경(확장/축소)이 필요한 경우 기존 매핑/뷰가 유효하지 않을 수 있습니다.
뷰를 모두 해제한 뒤 파일 크기를 조정하고, 새 매핑과 뷰를 다시 생성하세요.
여러 스레드가 같은 뷰를 쓸 때는 메모리 장벽과 잠금을 통해 레이스 컨디션을 방지해야 합니다.

💎 핵심 포인트:
파일을 메모리에 매핑해 빠른 I/O를 구현하려면, 권한 최소화·부분 뷰·정렬·플러시 전략을 기본으로 삼고 접근 패턴에 맞춘 뷰 크기 튜닝으로 추가 최적화를 진행하면 됩니다.

🔗 프로세스 간 데이터 공유

메모리 맵 파일은 단일 프로세스 내 빠른 I/O뿐 아니라, 서로 다른 프로세스 간에도 데이터를 공유할 수 있는 강력한 기능을 제공합니다.
같은 이름의 매핑 객체를 여러 프로세스에서 열면, 운영체제는 동일한 물리 메모리 영역을 참조하게 하여 복사 없이 데이터를 주고받을 수 있습니다.
이 덕분에 소켓이나 파이프 없이도 대용량 데이터를 실시간으로 전달할 수 있어 IPC(Inter-Process Communication)에서 자주 활용됩니다.

📌 동작 방식

1) 첫 번째 프로세스에서 CreateFileMapping으로 이름이 지정된 매핑 객체를 생성합니다.
2) 다른 프로세스는 OpenFileMapping을 호출하여 동일한 이름의 매핑 객체를 엽니다.
3) 두 프로세스 모두 MapViewOfFile로 해당 매핑을 가상 메모리에 연결합니다.
4) 한 쪽에서 데이터를 쓰면, 다른 쪽에서 즉시 변경 내용을 확인할 수 있습니다.

CODE BLOCK
// 프로세스 A - 생성
HANDLE hMap = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 
    0, 1024, L"MySharedMemory");
LPVOID pView = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
strcpy((char*)pView, "Hello from Process A");

// 프로세스 B - 열기
HANDLE hMapB = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, L"MySharedMemory");
LPVOID pViewB = MapViewOfFile(hMapB, FILE_MAP_ALL_ACCESS, 0, 0, 0);
printf("%s", (char*)pViewB); // "Hello from Process A"

⚡ 장점과 활용 예

  • 🚀소켓·파이프보다 낮은 지연시간과 높은 대역폭
  • 📂대규모 데이터 버퍼 공유(이미지, 비디오 프레임 등)
  • 🧩프로세스 간 상태 정보·메타데이터 교환

⚠️ 주의: 동시 쓰기 작업이 발생할 경우 데이터 충돌이 일어날 수 있습니다.
뮤텍스나 세마포어를 활용해 동기화를 반드시 처리해야 하며, 보안 권한 설정을 통해 무단 접근을 방지하는 것이 중요합니다.

💎 핵심 포인트:
메모리 맵 파일 기반 IPC는 초고속 데이터 전달이 가능하지만, 동기화와 보안 설정을 반드시 병행해야 안전하고 안정적으로 동작합니다.



🛠️ 실무 활용 사례와 주의사항

메모리 맵 파일은 단순한 예제 코드를 넘어, 실무에서도 다양한 시나리오에서 활용됩니다.
대규모 로그 분석, 고속 이미지 처리, 데이터베이스 엔진의 버퍼 캐시, 게임 클라이언트의 리소스 로딩, 그리고 동영상 편집 소프트웨어의 타임라인 버퍼까지 폭넓게 쓰입니다.
실시간성이 중요한 금융·통신 분야에서는 대규모 데이터 스트림을 지연 없이 전달하기 위해서도 활용됩니다.

📌 실무 활용 예

  • 📂데이터베이스 인덱스 페이지를 매핑하여 초고속 검색 구현
  • 🎮게임 리소스(텍스처·사운드) 로딩 최적화
  • 🖼️이미지·영상 편집 시 대용량 버퍼 공유
  • 📡실시간 데이터 피드 처리(금융 시세, 센서 데이터)

⚠️ 사용 시 주의사항

⚠️ 주의: 메모리 맵 파일은 성능이 뛰어나지만 잘못 사용하면 심각한 문제를 야기할 수 있습니다.
다음 사항을 반드시 확인하세요.

  • 🔒프로세스 간 공유 시 동기화 메커니즘 필수
  • 🛡️보안 권한 설정으로 무단 접근 차단
  • 📏Allocation Granularity에 맞춘 오프셋 정렬
  • 📉불필요한 전체 매핑은 메모리 부족과 페이지 폴트를 유발

💡 성능 유지 팁

💡 TIP: 데이터 접근 패턴 분석 후, 순차 읽기·쓰기라면 큰 버퍼로, 랜덤 접근이라면 작은 부분 매핑으로 최적화하세요.
Flush 호출은 변경이 쌓인 뒤 한 번에 처리하는 것이 성능 유지에 유리합니다.

💎 핵심 포인트:
메모리 맵 파일은 잘만 활용하면 디스크 I/O를 획기적으로 줄일 수 있지만, 동기화·보안·메모리 관리 전략이 함께 설계되어야 안정적이고 효율적인 운영이 가능합니다.

자주 묻는 질문 (FAQ)

메모리 맵 파일과 일반 파일 I/O의 가장 큰 차이는 무엇인가요?
메모리 맵 파일은 운영체제가 파일을 메모리에 직접 매핑하여, 디스크 읽기·쓰기 호출 없이도 데이터를 접근할 수 있습니다. 반면 일반 I/O는 ReadFile, WriteFile API 호출을 통해 커널을 거쳐야 하므로 지연이 더 큽니다.
대용량 파일 전체를 매핑해도 괜찮을까요?
가능은 하지만 권장되지 않습니다. 필요 없는 부분까지 메모리에 로드되면 메모리 부족과 페이지 폴트가 발생할 수 있습니다. 필요한 범위만 부분 매핑하는 것이 안전합니다.
프로세스 간 메모리 맵 파일 공유 시 보안 문제는 없나요?
이름이 지정된 매핑 객체는 다른 프로세스에서 접근할 수 있으므로, 보안 속성을 설정해 무단 접근을 차단하는 것이 중요합니다. 또한 동기화 객체를 함께 사용해 데이터 충돌을 방지해야 합니다.
FlushViewOfFile을 꼭 호출해야 하나요?
읽기 전용 매핑은 필요 없습니다. 쓰기 매핑의 경우 데이터 영속성이 필요하다면 FlushViewOfFile과 FlushFileBuffers를 호출해 디스크에 반영해야 합니다.
페이지 파일 기반 매핑은 언제 쓰는 게 좋나요?
디스크에 저장할 필요가 없는 임시 데이터나 프로세스 간 공유 버퍼가 필요할 때 유용합니다. 프로그램 종료 시 데이터가 자동 삭제되므로 민감 정보 관리에도 적합합니다.
MapViewOfFile에서 매핑 크기를 0으로 주면 무슨 의미인가요?
0을 지정하면 매핑 객체의 끝까지 모든 데이터를 뷰에 매핑합니다. 하지만 대용량 파일에서는 메모리 부족 위험이 있으므로 권장하지 않습니다.
메모리 맵 파일 사용 시 동기화 방법은 무엇이 있나요?
뮤텍스(Mutex), 세마포어(Semaphore), 이벤트(Event) 객체 등을 활용해 동기화할 수 있습니다. 상황에 맞는 동기화 도구를 선택하는 것이 중요합니다.
리눅스에서도 같은 방식으로 사용할 수 있나요?
네, 리눅스에서는 mmap 함수를 사용하여 비슷한 기능을 제공합니다. 다만 API와 권한 설정 방식이 다르므로 운영체제별로 구현을 맞춰야 합니다.

📌 WinAPI 메모리 맵 파일로 구현하는 고속 I/O 핵심 정리

메모리 맵 파일은 파일을 메모리에 직접 매핑해 디스크 I/O를 최소화하고, 프로세스 간 데이터를 고속으로 공유할 수 있는 강력한 기능입니다.
WinAPI의 CreateFileMappingMapViewOfFile을 기반으로, 디스크 기반과 페이지 파일 기반 두 가지 방식으로 구현할 수 있습니다.
실무에서는 대규모 로그 처리, 멀티미디어 편집, 게임 리소스 로딩, 금융 데이터 스트림 등 속도와 효율이 중요한 다양한 환경에서 널리 활용됩니다.

다만, 동기화와 보안 설정, 메모리 관리 전략이 함께 설계되지 않으면 데이터 충돌, 메모리 부족, 보안 취약점이 발생할 수 있습니다.
부분 매핑, 접근 권한 최소화, Flush 전략 최적화, 동기화 객체 사용을 기본 원칙으로 삼으면 안정적이고 고성능의 데이터 처리가 가능합니다.
운영체제와 하드웨어 구조를 이해하고, 접근 패턴에 맞춰 튜닝하는 것이 성능 극대화의 핵심입니다.


🏷️ 관련 태그 : 메모리맵파일, WinAPI, 파일입출력, IPC, 대용량데이터, 성능최적화, Windows프로그래밍, 시스템프로그래밍, 부분매핑, 페이지파일