8000 ENH: Add `madvise` for `memmap` objects by IndifferentArea · Pull Request #29260 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

ENH: Add madvise for memmap objects #29260

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions numpy/_core/memmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,63 @@ def flush(self):
if self.base is not None and hasattr(self.base, 'flush'):
self.base.flush()

def madvise(self, option):
"""
Give advice about the memory region corresponding to this array.

Parameters
----------
option : int
One of the `madvise` options, e.g., `mmap.MADV_NORMAL`,
`mmap.MADV_RANDOM`, `mmap.MADV_SEQUENTIAL`, `mmap.MADV_WILLNEED`,
`mmap.MADV_DONTNEED`, `mmap.MADV_FREE`, `mmap.MADV_REMOVE`,
`mmap.MADV_DONTFORK`, `mmap.MADV_DOFORK`, `mmap.MADV_MERGEABLE`,
`mmap.MADV_UNMERGEABLE`, `mmap.MADV_HUGEPAGE`,
`mmap.MADV_NOHUGEPAGE`, `mmap.MADV_SOFT_OFFLINE`,
`mmap.MADV_COLD`, `mmap.MADV_PAGEOUT`, `mmap.MADV_POPULATE`.

Raises
------
TypeError
If the memory map is not backed by any memory (e.g., already closed)
or if `madvise` is called on a view of a memmap.
OSError
If the underlying `mmap.madvise` call fails.
NotImplementedError
If `mmap.madvise` is not available on the current system or Python version.

Notes
-----
This method is only available on Python 3.8+ and systems that support
the `madvise` system call.

See Also
--------
mmap.madvise : The underlying `mmap` method.

"""
if self._mmap is None:
raise TypeError(
"madvise cannot be used: the memory map is not backed by any memory."
)

# Reject madvise on views for simplicity, as their memory range might not
# align with page boundaries and behavior can be complex.
# TODO: support madvise on view of mmap
if self.base is not self._mmap:
raise TypeError(
"madvise cannot be used on a view of a memmap object. "
"Please use it on the original memmap object."
)

try:
self._mmap.madvise(option)
except AttributeError:
# This can happen if mmap.madvise is not available on the system
raise NotImplementedError("mmap.madvise is not available on this system.")
except OSError as e:
raise OSError(e.errno, e.strerror + ". Check if 'option' is valid.") from e

def __array_wrap__(self, arr, context=None, return_scalar=False):
arr = super().__array_wrap__(arr, context)

Expand Down
28 changes: 28 additions & 0 deletions numpy/_core/tests/test_memmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,31 @@ def test_shape_type(self):
memmap(self.tmpfp, shape=self.shape, mode='w+')
memmap(self.tmpfp, shape=list(self.shape), mode='w+')
memmap(self.tmpfp, shape=asarray(self.shape), mode='w+')

@pytest.mark.skipif(not hasattr(mmap, "MADV_NORMAL") and hasattr(mmap, "madvise"),
reason="mmap.MADV_NORMAL is not available on this system.")
def test_madvise_normal(self):
fp = memmap(self.tmpfp, dtype=self.dtype, mode='w+', shape=self.shape)
fp[:] = self.data[:]
# This should not raise an error
fp.madvise(mmap.MADV_NORMAL)

def test_madvise_no_mmap(self):
fp = memmap(self.tmpfp, dtype=self.dtype, mode='w+', shape=self.shape)
# Manually set _mmap to None to simulate a closed/unbacked memmap
fp._mmap = None
expected_err_msg = (
"madvise cannot be used: the memory map is not backed by any memory."
)
with pytest.raises(TypeError, match=expected_err_msg):
fp.madvise(mmap.MADV_NORMAL)

def test_madvise_on_view(self):
fp = memmap(self.tmpfp, dtype=self.dtype, mode='w+', shape=self.shape)
view = fp[1:] # Create a view
expected_err_msg = (
"madvise cannot be used on a view of a memmap object. "
"Please use it on the original memmap object."
)
with pytest.raises(TypeError, match=expected_err_msg):
view.madvise(mmap.MADV_NORMAL)
Loading
0