| 属性 | 内容 |
|---|---|
| 包地址 | go.dw1.io/mmapfile |
| 定位 | *os.File 的高性能替代品 |
| 核心技术 | 内存映射 I/O (Memory-Mapped I/O) |
| 设计哲学 | Drop-in replacement,最小化代码改动 |
| 许可证 | 开源 (推测为 MIT/BSD) |
| 作者 | dw1 |
| 状态 | Pre-1.0 (API 可能变化) |
┌─────────────────────────────────────────────────────────────────┐
│ MmapFile │
├─────────────────────────────────────────────────────────────────┤
│ API Layer (os.File 兼容接口) │
│ ├── Open(path) → *MmapFile │
│ ├── OpenFile(path, flag, perm, size) → *MmapFile │
│ ├── Read(buf) → (n, error) // 带游标 │
│ ├── Write(buf) → (n, error) // 带游标 │
│ ├── ReadAt(buf, off) → (n, error) // 并发安全 │
│ ├── WriteAt(buf, off) → (n, error) // 并发安全 │
│ ├── Seek(off, whence) → (newOff, error) │
│ ├── Bytes() → []byte // 零拷贝核心 │
│ ├── Sync() → error // 刷盘 │
│ └── Close() → error // 解除映射 │
├─────────────────────────────────────────────────────────────────┤
│ Implementation Layer │
│ ├── syscall.Mmap (底层映射) │
│ ├── 游标管理 (offset int64) │
│ ├── 读写锁 (RWMutex) │
│ └── 平台适配 (Linux/macOS/Windows) │
└─────────────────────────────────────────────────────────────────┘
os.File MmapFile
─────────────────────────────────────────────────
fd (文件描述符) addr (内存地址)
syscall.Read 直接内存读取
copy 到用户缓冲区 直接访问页缓存
每次 I/O 系统调用 零系统调用 (除初始化)
动态文件大小 固定文件大小
// 只读打开(最常用)
f, err := mmapfile.Open("data.txt")
// 创建/打开文件(需指定大小)
f, err := mmapfile.OpenFile(
"data.bin", // 文件名
os.O_RDWR|os.O_CREATE, // 标志
0644, // 权限
1024*1024*100, // 大小:100MB(必须指定)
)
关键限制: 文件大小在打开时确定,无法动态扩展。
// 获取底层内存切片
data := f.Bytes()
// 直接读写,无拷贝
value := binary.BigEndian.Uint32(data[100:104])
binary.BigEndian.PutUint64(data[200:208], timestamp)
// 警告:只读文件写入会导致崩溃!
// ReadAt/WriteAt 是并发安全的
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(offset int64) {
defer wg.Done()
buf := make([]byte, 100)
f.ReadAt(buf, offset) // ✅ 安全并发
}(int64(i * 100))
}
wg.Wait()
// Read/Write/Seek 共享游标,需加锁
f.Seek(0, io.SeekStart) // 非并发安全
| 操作 | 传统 *os.File | mmapfile | 加速比 |
|---|---|---|---|
| 读取 1KB | 基准 | - | 50x |
| 读取 1GB | 基准 | - | 3x |
| 并行读取 | 基准 | - | 10-12x |
| 写入 1KB | 基准 | - | 51x |
| 写入 100KB | 基准 | - | 6x |
| Seek 操作 | 基准 | - | 29x |
| WriteTo | 基准 | - | 254x |
| 总体平均 | 基准 | - | ~6x |
传统 I/O:
用户空间 → syscall(read) → 内核 → 页缓存 → 拷贝到用户缓冲区 → 用户空间
↑ 上下文切换 200ns+ ↑ 内存拷贝
mmapfile I/O:
用户空间 → 直接访问页缓存 → 用户空间
↑ 无系统调用,无拷贝
type MmapFile struct {
data []byte // 映射的内存区域
offset int64 // 游标(非线程安全)
mu sync.RWMutex // 保护游标操作
// ...
}
// ReadAt - 无锁,并发安全
func (f *MmapFile) ReadAt(p []byte, off int64) (n int, err error) {
// 直接内存拷贝,无需加锁
return copy(p, f.data[off:]), nil
}
// Read - 需加锁保护游标
func (f *MmapFile) Read(p []byte) (n int, err error) {
f.mu.Lock()
n, err = f.ReadAt(p, f.offset)
f.offset += int64(n)
f.mu.Unlock()
return
}
| 平台 | 实现方式 | 备注 |
|---|---|---|
| Linux | syscall.Mmap | 原生支持 |
| macOS | syscall.Mmap | 原生支持 |
| Windows | CreateFileMapping + MapViewOfFile | 模拟实现 |
| 其他 Unix | syscall.Mmap | 可能不支持 |
// 1. 数据库引擎(如 BoltDB、LMDB)
// 快速随机访问数据页
type Page struct { ID uint64; Data [4096]byte }
page := (*Page)(unsafe.Pointer(&f.Bytes()[pageID*4096]))
// 2. 大文件日志分析
// 直接扫描 GB 级日志文件
for off := int64(0); off < int64(f.Len()); {
line := scanLine(f.Bytes(), off)
process(line)
off += int64(len(line))
}
// 3. 配置文件/静态资源
// 快速加载和解析
json.Unmarshal(f.Bytes(), &config)
// 4. 进程间共享内存
// 多个进程映射同一文件
f, _ := mmapfile.OpenFile("/dev/shm/shared", os.O_RDWR, 0666, size)
// 1. 流式数据处理
// 网络流、管道等无法确定大小的数据
stream, _ := net.Dial("tcp", addr) // ❌
// 2. 需要频繁扩容的文件
// 日志追加、数据库 WAL 等
log.Write(line) // 需要 append 模式,不支持 ❌
// 3. 超大顺序写入
// 内核缓冲策略更适合连续大写入
writeBigFile sequentially // ❌ 略慢于标准 I/O
// 4. 临时小文件
// 初始化开销不值得
ioutil.WriteFile("/tmp/small.txt", data, 0644) // ❌
package main
import (
"encoding/binary"
"go.dw1.io/mmapfile"
)
type MMapDB struct {
f *mmapfile.MmapFile
}
const RecordSize = 128
func OpenDB(path string, numRecords int) (*MMapDB, error) {
size := int64(numRecords * RecordSize)
f, err := mmapfile.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644, size)
if err != nil {
return nil, err
}
return &MMapDB{f: f}, nil
}
func (db *MMapDB) ReadRecord(id int) []byte {
data := db.f.Bytes()
offset := id * RecordSize
return data[offset : offset+RecordSize]
}
func (db *MMapDB) WriteRecord(id int, record []byte) {
data := db.f.Bytes()
offset := id * RecordSize
copy(data[offset:], record)
}
func (db *MMapDB) Sync() error {
return db.f.Sync() // 刷盘
}
type ConfigCache struct {
f *mmapfile.MmapFile
parsed atomic.Value // 存储 *Config
}
func (c *ConfigCache) Reload() error {
f, err := mmapfile.Open("config.json")
if err != nil {
return err
}
var cfg Config
if err := json.Unmarshal(f.Bytes(), &cfg); err != nil {
f.Close()
return err
}
old := c.f
c.parsed.Store(&cfg)
c.f = f
if old != nil {
old.Close() // 原子替换后关闭旧文件
}
return nil
}
func ProcessBigFileParallel(path string, workers int) error {
f, err := mmapfile.Open(path)
if err != nil {
return err
}
defer f.Close()
size := f.Len()
chunkSize := size / workers
var wg sync.WaitGroup
for i := 0; i < workers; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
start := int64(id * chunkSize)
end := start + int64(chunkSize)
if id == workers-1 {
end = int64(size) // 最后一个 worker 处理剩余部分
}
buf := make([]byte, end-start)
f.ReadAt(buf, start) // ✅ 并发安全
processChunk(buf)
}(i)
}
wg.Wait()
return nil
}
| 库 | 易用性 | 功能 | 性能 | 成熟度 |
|---|---|---|---|---|
| mmapfile | ⭐⭐⭐⭐⭐ | 中等 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| edsrzf/mmap-go | ⭐⭐⭐⭐ | 基础 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| golang.org/x/exp/mmap | ⭐⭐⭐ | 简单 | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 手写 syscall | ⭐⭐ | 完整 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
mmapfile 优势:
os.Open 即可)f, err := mmapfile.Open("data.bin")
if err != nil {
return err
}
defer f.Close() // 必须!否则内存泄漏
// ✅ 好的做法:并发安全,性能更好
buf := make([]byte, 100)
n, _ := f.ReadAt(buf, offset)
// ⚠️ 一般做法:需要加锁才能并发
f.Seek(offset, io.SeekStart)
n, _ := f.Read(buf)
// 创建时预留足够空间
const MaxRecords = 1000000
const RecordSize = 256
f, _ := mmapfile.OpenFile("db.bin", os.O_CREATE|os.O_RDWR, 0644,
MaxRecords*RecordSize)
// 关键数据写入后立即刷盘
f.WriteAt(data, offset)
f.Sync() // 确保写入磁盘
mmapfile 是 Go 生态中最易用的内存映射文件库,它将复杂的 mmap 操作封装为熟悉的 *os.File 接口。
os.Open → mmapfile.Open研究时间: 2026-03-07
来源: dw1.io/blog, pkg.go.dev
标签: #Go #mmapfile #内存映射 #高性能IO #零拷贝
还没有人回复