Memory-Mapped Files
debt(d9/e5/b5/t7)
Closest to 'silent in production until users hit it' (d9). No detection_hints.tools are listed. mmap misuse — such as random single-byte reads causing page fault storms, or dirty page writeback latency causing data loss — produces no compile-time or linter warnings. Problems surface only as performance degradation or data integrity issues observed by end users under load.
Closest to 'touches multiple files / significant refactor in one component' (e5). There is no quick_fix provided. Correcting mmap misuse (e.g., switching from mmap to buffered read(), adjusting opcache.memory_consumption, or handling writeback latency with msync()) requires understanding of the I/O access pattern, changes to how files are opened and read, and potentially configuration changes across deployment. This is more than a one-liner but typically contained to a subsystem.
Closest to 'persistent productivity tax' (b5). mmap choices affect how processes share memory (IPC), how opcache behaves across all FPM workers, and how large-file I/O is architected. This is not localised to a single call site — incorrect sizing or usage patterns impose a tax on every future developer who must reason about page faults, TLB pressure, and writeback semantics. However, it does not fully define the system's shape.
Closest to 'serious trap (contradicts how a similar concept works elsewhere)' (t7). The canonical misconception is explicit: developers assume mmap is always faster than read() because it avoids a syscall per read, but the page fault overhead and TLB pressure for random single reads can make it slower. This directly contradicts the widely-held mental model that memory access is inherently faster than I/O syscalls, making it a serious cognitive trap for competent developers unfamiliar with the nuance.
TL;DR
Explanation
Memory mapping (mmap on Linux) creates a direct mapping between a file on disk and a region of virtual memory. When a process reads from that address range, the OS transparently loads the relevant page from disk via a page fault. Writes modify the page in memory and the OS flushes them to disk asynchronously. This eliminates the user-to-kernel copy that standard file I/O requires, reducing syscall overhead for large sequential reads. Multiple processes mapping the same file share the underlying pages, enabling efficient IPC (inter-process communication) without explicit message passing. PHP itself does not expose mmap directly, but it is used internally by opcache to share the compiled opcode cache between FPM workers — all workers read from the same shared memory region rather than each maintaining a private copy. SQLite uses mmap for its WAL-mode readers. PHP extensions can use mmap via FFI or C extensions for performance-critical work.
Watch Out
Common Misconception
Why It Matters
Common Mistakes
- Assuming mmap is always the fastest I/O method — random single-byte reads across a large mapping cause repeated page faults that are slower than buffered read().
- Mapping very large files on 32-bit systems — virtual address space is limited to ~3 GB, making large mappings impractical or impossible.
- Not accounting for dirty page writeback latency — a write to a mapped page is not on disk until the OS flushes it, which is asynchronous by default.
Avoid When
- Avoid mmap for small files — the setup overhead and page table entries outweigh the benefit vs file_get_contents().
- Do not use mmap for append-heavy workloads — extending a mapping requires re-mapping, which is expensive.
When To Use
- Use mmap for large sequential reads of files that exceed available RAM — the OS streams pages in and evicts them transparently.
- Use shared anonymous mappings for IPC between processes on the same host where a message queue would add unnecessary overhead.
- Tune opcache.memory_consumption based on your codebase size — it uses mmap internally and insufficient size causes re-compilation.
Code Examples
; opcache too small — workers silently recompile scripts on every request:
opcache.memory_consumption = 64 ; MB — too small for a 500-file app
opcache.max_accelerated_files = 2000
; Size to fit entire compiled codebase + headroom:
; Check current usage: opcache_get_status()['memory_usage']
opcache.memory_consumption = 256 ; MB
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 0 ; production: disable stat() per request