Skip to main content

mecab_ko_dict/matrix/
mmap.rs

1//! 메모리 맵 연접 비용 행렬 (Memory-Mapped Connection Cost Matrix)
2
3use std::path::Path;
4
5use crate::error::{DictError, Result};
6
7use super::{dense::DenseMatrix, parse_matrix_header, Matrix, INVALID_CONNECTION_COST};
8
9/// 메모리 맵 연접 비용 행렬 (Memory-Mapped Matrix)
10///
11/// 대용량 행렬을 메모리 맵으로 로드하여 효율적으로 접근합니다.
12/// 프로세스 간 메모리 공유가 가능합니다.
13///
14/// # Safety
15///
16/// 이 구조체는 메모리 맵을 사용하므로 내부적으로 unsafe 코드가 필요합니다.
17/// 파일이 외부에서 수정되지 않아야 합니다.
18pub struct MmapMatrix {
19    /// 좌문맥 크기
20    lsize: usize,
21    /// 우문맥 크기
22    rsize: usize,
23    /// 헤더 크기 (v2: 4, v3: 16)
24    header_size: usize,
25    /// 메모리 맵
26    mmap: memmap2::Mmap,
27}
28
29impl MmapMatrix {
30    /// 바이너리 파일에서 메모리 맵으로 로드
31    ///
32    /// # Safety
33    ///
34    /// 파일이 외부에서 수정되지 않아야 합니다.
35    /// memmap2는 파일을 메모리에 매핑하며, 이는 본질적으로 unsafe입니다.
36    ///
37    /// # Errors
38    ///
39    /// 파일을 읽거나 메모리 맵을 생성할 수 없는 경우 에러를 반환합니다.
40    #[allow(unsafe_code)]
41    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
42        let file = std::fs::File::open(path.as_ref()).map_err(DictError::Io)?;
43
44        // SAFETY: 파일이 열려 있는 동안 수정되지 않는다고 가정
45        // memmap2::Mmap::map은 파일 내용이 변경되지 않을 때 안전합니다.
46        let mmap = unsafe { memmap2::Mmap::map(&file).map_err(DictError::Io)? };
47
48        // 헤더만 파싱할 만큼만 전달
49        let header = parse_matrix_header(&mmap)?;
50
51        let expected_size = header.header_size + header.lsize * header.rsize * 2;
52        if mmap.len() != expected_size {
53            return Err(DictError::Format(format!(
54                "Matrix file size mismatch: expected {} bytes, got {}",
55                expected_size,
56                mmap.len()
57            )));
58        }
59
60        Ok(Self {
61            lsize: header.lsize,
62            rsize: header.rsize,
63            header_size: header.header_size,
64            mmap,
65        })
66    }
67
68    /// 압축된 파일에서 로드 (메모리에 전체 압축 해제)
69    ///
70    /// 압축 파일은 메모리 맵이 아닌 전체 압축 해제 후 로드됩니다.
71    ///
72    /// # Errors
73    ///
74    /// 파일을 읽거나 압축 해제할 수 없는 경우 에러를 반환합니다.
75    pub fn from_compressed_file<P: AsRef<Path>>(path: P) -> Result<DenseMatrix> {
76        // 압축 파일은 DenseMatrix로 로드
77        DenseMatrix::from_compressed_file(path)
78    }
79
80    #[inline]
81    const fn offset(&self, right_id: u16, left_id: u16) -> usize {
82        self.header_size + (right_id as usize + self.lsize * left_id as usize) * 2
83    }
84}
85
86impl Matrix for MmapMatrix {
87    #[inline(always)]
88    fn get(&self, right_id: u16, left_id: u16) -> i32 {
89        let offset = self.offset(right_id, left_id);
90        if offset + 2 <= self.mmap.len() {
91            let bytes = [self.mmap[offset], self.mmap[offset + 1]];
92            i32::from(i16::from_le_bytes(bytes))
93        } else {
94            INVALID_CONNECTION_COST
95        }
96    }
97
98    fn left_size(&self) -> usize {
99        self.lsize
100    }
101
102    fn right_size(&self) -> usize {
103        self.rsize
104    }
105}