MemoryMap#
- class scikitplot.memmap.MemoryMap#
Memory-mapped region with automatic resource management.
This class wraps a memory-mapped region and provides a Pythonic interface with context manager support for automatic cleanup.
- Parameters:
- addrint
Memory address returned from mmap()
- sizeint
Size of the mapping in bytes
- Attributes:
Notes
Always use factory methods (create_*) to create instances
Use as context manager for automatic cleanup
Accessing closed mapping raises ValueError
The mapping exports its bytes through the buffer protocol, so
memoryview(m)views it without copying. Amemoryviewand an array fromas_numpy_arrayeach hold an export, which keeps the mapped region alive:closeends this object’s own API at once, and the region is unmapped when the last export is released. Using such a view afterclose()is therefore safe.numpy.frombuffer(m)called directly holds an export from NumPy 1.23 on (NumPy keeps a memoryview of its own as the array’s base); older NumPy, still allowed on Python 3.8, releases it at once, andclose()may then unmap under the array.as_numpy_arrayholds one with every NumPy.An array’s
basememoryview is its export: callingarr.base.release()while the array is still used invalidates the array, as it does for any NumPy array over a released buffer.A mapping without
PROT_READexports nothing (BufferError) and cannot be read; access through it would crash the process.Methods that release the GIL around a system call or a copy (
read,write,msync,mlock,munlock) hold the same kind of export while they run, so aclosefrom another thread defers the unmap instead of unmapping under them.mprotectkeeps the GIL for its system call, so the protection and the recorded flags change together, and it refuses to remove a protection bit while exports are alive (BufferError): a view exported as writable would crash the process on its next write.Threads may share one mapping, on free-threaded CPython builds too: the open/closed state, the export count and the protection flags change under a lock of the mapping, so an export or a method never starts on a closed mapping, and the region is unmapped once, after the last export. What the threads write into the mapped bytes is theirs to order, as for any shared memory.
Examples
>>> with MemoryMap.create_anonymous(4096, PROT_READ | PROT_WRITE) as m: ... m.write(b"test") ... data = m.read(4)
- addr#
int
Get memory address of mapped region.
- Returns:
- int
Memory address as integer
- Raises:
- ValueError
If mapping is closed
- Type:
- as_numpy_array(self, dtype=None)#
Return a NumPy array that shares memory with this mapping.
No data is copied. The array holds a buffer export of this mapping, so the mapped region stays alive as long as the array (or any view of it) does, even after
close.- Parameters:
- dtypenumpy.dtype or None, optional
Desired element type of the output array. When None (default) the raw view is returned as
numpy.uint8. Any dtype whoseitemsizeevenly dividesself.sizeis accepted.
- Returns:
- numpy.ndarray
A 1-D array viewing the mapped memory. It is writeable only when the mapping was created with
PROT_WRITE.
- Raises:
- ValueError
If the mapping is closed, or if
dtype.itemsizedoes not evenly divide the mapping size.- ImportError
If NumPy is not installed.
Notes
Developer. The array is built with
numpy.frombufferover this object’s buffer protocol, which counts exports;closedefers themunmapwhile any export is alive. The previous implementation wrapped the raw address in actypesbuffer: that kept this object alive but not the region, becauseclose(called by everywithblock) unmapped it regardless, and touching the array afterwards dereferenced unmapped memory (SIGSEGV).Examples
>>> import numpy as np >>> with MemoryMap.create_anonymous(4096) as m: ... arr = m.as_numpy_array() # uint8 view ... arr[:5] = [72, 101, 108, 108, 111] ... print(m.read(5)) b'Hello' >>> int(arr[0]) # still valid after the with block closed m 72
Reinterpret as 32-bit floats:
>>> with MemoryMap.create_anonymous(4096) as m: ... arr = m.as_numpy_array(dtype=np.float32) ... arr[0] = 3.14
- close(self) None#
Close the memory mapping.
Unmaps the region and releases resources.
- Raises:
- MMapError
If munmap() fails; the mapping then stays open.
- Return type:
None
Notes
After closing, the mapping cannot be used
Called automatically by __dealloc__ or context manager
Safe to call multiple times (idempotent)
If views (
as_numpy_array,memoryview) are still alive, or another thread is inside a method that runs without the GIL (read,write, …), the unmap is deferred until the last of them is released. It used to unmap at once: reading such a view – for instance an array returned from awithblock – or areadrunning on another thread killed the process with SIGSEGV.If
munmapfails, the mapping is open again when this raises. Until then other threads see it closed: their exports and methods are refused, and a secondclose()returns at once.
Examples
>>> m = MemoryMap.create_anonymous(4096) >>> m.close() >>> m.is_valid False
- static create_anonymous(int size: int, int prot: int = 0x3, int flags: int = 2) MemoryMap#
Create anonymous memory mapping (not backed by file).
- Parameters:
- sizeint
Size of the mapping in bytes. Must be > 0.
- protint, optional
Memory protection flags (PROT_READ, PROT_WRITE, etc.). Default: PROT_READ | PROT_WRITE
- flagsint, optional
Mapping flags (should include MAP_PRIVATE or MAP_SHARED). Default: MAP_PRIVATE
- Returns:
- MemoryMap
New memory-mapped region
- Raises:
- ValueError
If size <= 0 or invalid flags
- MMapAllocationError
If mapping allocation fails
- Parameters:
- Return type:
Notes
Memory is initially zero-filled
Anonymous mappings are not backed by any file
Useful for inter-process communication with MAP_SHARED
Examples
>>> m = MemoryMap.create_anonymous(4096, PROT_READ | PROT_WRITE) >>> m.write(b"Hello") >>> m.close()
Using context manager (recommended):
>>> with MemoryMap.create_anonymous(4096) as m: ... m.write(b"Hello, World!")
- static create_file_mapping(int fd: int, int offset: int, int size: int, int prot: int = 1, int flags: int = 2) MemoryMap#
Create file-backed memory mapping.
- Parameters:
- fdint
File descriptor of open file
- offsetint
Offset in file to start mapping (must be page-aligned)
- sizeint
Size of the mapping in bytes. Must be > 0.
- protint, optional
Memory protection flags. Default: PROT_READ
- flagsint, optional
Mapping flags. Default: MAP_PRIVATE
- Returns:
- MemoryMap
New memory-mapped region
- Raises:
- ValueError
If fd < 0, size <= 0, or invalid flags
- MMapAllocationError
If mapping allocation fails
- Parameters:
- Return type:
Notes
File must be opened with appropriate permissions
offset must be page-aligned (typically 4096 bytes)
MAP_SHARED changes are written back to file
MAP_PRIVATE creates copy-on-write mapping
Examples
>>> with open("data.bin", "r+b") as f: ... m = MemoryMap.create_file_mapping(f.fileno(), 0, 4096, PROT_READ) ... data = m.read(100) ... m.close()
- is_valid#
bool
Check if mapping is still valid.
- Returns:
- bool
True if valid, False if closed
- Type:
- mlock(self) None#
Lock mapped pages in physical memory (prevent swapping).
- Raises:
- ValueError
If the mapping is already closed.
- MMapError
If the underlying
mlock()system call fails. On Linux this commonly means the process has exceeded itsRLIMIT_MEMLOCKsoft limit; on Windows it may requireSE_LOCK_MEMORY_NAMEprivilege.
- Return type:
None
Notes
Locking pages is useful for latency-sensitive code (e.g. real-time signal processing) that cannot tolerate page faults. Remember to call
munlockwhen the guarantee is no longer needed; otherwise the locked pages count against the process resource limit for the lifetime of the mapping.Examples
>>> with MemoryMap.create_anonymous(4096) as m: ... m.mlock() # pages will not be swapped out ... m.write(b"latency-critical data") ... m.munlock() # release the lock
- mprotect(self, int prot: int) None#
Change memory protection of mapped region.
- Parameters:
- protint
New protection flags
- Raises:
- ValueError
If mapping is closed or flags invalid
- BufferError
If prot removes a protection bit while views of the mapping (
memoryview,as_numpy_array) are alive, or while another thread runsread,write,msync,mlockormunlockon it: a view exported as writable or readable, or the running copy, would crash the process on its next access. Adding bits is always allowed.- MMapError
If mprotect() fails
- Parameters:
prot (int)
- Return type:
None
Examples
>>> with MemoryMap.create_anonymous(4096, PROT_READ) as m: ... m.mprotect(PROT_READ | PROT_WRITE) ... m.write(b"Now writable!")
- msync(self, int flags: int = 2) None#
Synchronize mapped region with backing storage.
- Parameters:
- flagsint, optional
Sync flags (MS_ASYNC, MS_SYNC, MS_INVALIDATE). Default: MS_SYNC
- Raises:
- ValueError
If mapping is closed
- MMapError
If msync() fails
- Parameters:
flags (int)
- Return type:
None
Notes
Only meaningful for file-backed mappings
MS_SYNC blocks until sync complete
MS_ASYNC returns immediately
Examples
>>> with MemoryMap.create_file_mapping(fd, 0, 4096, PROT_WRITE, MAP_SHARED) as m: ... m.write(b"Data") ... m.msync(MS_SYNC) # Ensure written to disk
- munlock(self) None#
Unlock mapped pages (allow the kernel to swap them out again).
- Raises:
- ValueError
If the mapping is already closed.
- MMapError
If the underlying
munlock()system call fails.
- Return type:
None
Notes
This is the inverse of
mlock. Callingmunlockon pages that were never locked is a no-op on most platforms but the behaviour is technically undefined by POSIX; avoid it.Examples
>>> with MemoryMap.create_anonymous(4096) as m: ... m.mlock() ... # … do latency-critical work … ... m.munlock()
- page_size#
int
System page size in bytes.
- Returns:
- int
Page size as reported by the kernel (e.g. 4096 or 16384).
Notes
File-mapping offsets passed to
create_file_mappingmust be multiples of this value. Use it for manual alignment:aligned = (raw_offset // m.page_size) * m.page_size
- Type:
- read(self, int size: int, int offset: int = 0) bytes#
Read bytes from mapped region.
- Parameters:
- sizeint
Number of bytes to read
- offsetint, optional
Offset from start of mapping. Default: 0
- Returns:
- bytes
Data read from mapping
- Raises:
- ValueError
If mapping is closed, not readable, or parameters are invalid
- Parameters:
- Return type:
Examples
>>> with MemoryMap.create_anonymous(4096) as m: ... _ = m.write(b"Hello") ... data = m.read(5) ... print(data) b'Hello'
- size#
int
Get size of mapped region.
- Returns:
- int
Size in bytes
- Raises:
- ValueError
If mapping is closed
- Type:
- write(self, bytes data: bytes, int offset: int = 0) int#
Write bytes to mapped region.
- Parameters:
- databytes
Data to write
- offsetint, optional
Offset from start of mapping. Default: 0
- Returns:
- int
Number of bytes written
- Raises:
- ValueError
If mapping is closed, not writable, or parameters invalid
- Parameters:
- Return type:
Examples
>>> with MemoryMap.create_anonymous(4096, PROT_WRITE) as m: ... n = m.write(b"Hello, World!") ... print(n) 13
Gallery examples#
Memory-Mapping Showcase – Basic / Medium / Advanced