npguard 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- npguard-0.1.0/LICENSE +21 -0
- npguard-0.1.0/PKG-INFO +61 -0
- npguard-0.1.0/README.md +39 -0
- npguard-0.1.0/npguard/__init__.py +13 -0
- npguard-0.1.0/npguard/core.py +203 -0
- npguard-0.1.0/npguard.egg-info/PKG-INFO +61 -0
- npguard-0.1.0/npguard.egg-info/SOURCES.txt +10 -0
- npguard-0.1.0/npguard.egg-info/dependency_links.txt +1 -0
- npguard-0.1.0/npguard.egg-info/requires.txt +1 -0
- npguard-0.1.0/npguard.egg-info/top_level.txt +1 -0
- npguard-0.1.0/pyproject.toml +41 -0
- npguard-0.1.0/setup.cfg +4 -0
npguard-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Priyanshu Rauth
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
npguard-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: npguard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: NumPy memory observability and explanation tool
|
|
5
|
+
Author: Priyanshu Rauth
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/npguard
|
|
8
|
+
Project-URL: Issues, https://github.com/yourusername/npguard/issues
|
|
9
|
+
Keywords: numpy,memory,profiling,performance,scientific-computing
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering
|
|
13
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# npguard
|
|
24
|
+
|
|
25
|
+
**npguard** is a NumPy memory observability and explanation tool.
|
|
26
|
+
|
|
27
|
+
It helps developers understand *why* NumPy memory usage spikes by detecting
|
|
28
|
+
temporary allocations and explaining their causes, with safe, opt-in suggestions
|
|
29
|
+
to reduce memory pressure.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Watch NumPy-heavy code blocks
|
|
34
|
+
- Detect memory pressure and hidden temporaries
|
|
35
|
+
- Explain causes (chained ops, broadcasting)
|
|
36
|
+
- Provide safe optimization suggestions
|
|
37
|
+
- No monkey-patching, no unsafe automation
|
|
38
|
+
|
|
39
|
+
## What npguard does not do
|
|
40
|
+
- Does not modify NumPy behavior
|
|
41
|
+
- Does not automatically reuse buffers
|
|
42
|
+
- Does not rewrite code
|
|
43
|
+
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import numpy as np
|
|
48
|
+
import npguard as ng
|
|
49
|
+
|
|
50
|
+
with ng.memory_watcher("matrix_pipeline"):
|
|
51
|
+
a = np.random.rand(10_000, 100)
|
|
52
|
+
ng.register_array(a, "a")
|
|
53
|
+
|
|
54
|
+
b = a * 2 + a.mean(axis=0) - 1
|
|
55
|
+
ng.register_array(b, "b")
|
|
56
|
+
|
|
57
|
+
c = np.ascontiguousarray(b.T)
|
|
58
|
+
ng.register_array(c, "c")
|
|
59
|
+
|
|
60
|
+
ng.report()
|
|
61
|
+
ng.suggest()
|
npguard-0.1.0/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# npguard
|
|
2
|
+
|
|
3
|
+
**npguard** is a NumPy memory observability and explanation tool.
|
|
4
|
+
|
|
5
|
+
It helps developers understand *why* NumPy memory usage spikes by detecting
|
|
6
|
+
temporary allocations and explaining their causes, with safe, opt-in suggestions
|
|
7
|
+
to reduce memory pressure.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Watch NumPy-heavy code blocks
|
|
12
|
+
- Detect memory pressure and hidden temporaries
|
|
13
|
+
- Explain causes (chained ops, broadcasting)
|
|
14
|
+
- Provide safe optimization suggestions
|
|
15
|
+
- No monkey-patching, no unsafe automation
|
|
16
|
+
|
|
17
|
+
## What npguard does not do
|
|
18
|
+
- Does not modify NumPy behavior
|
|
19
|
+
- Does not automatically reuse buffers
|
|
20
|
+
- Does not rewrite code
|
|
21
|
+
|
|
22
|
+
## Example
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import numpy as np
|
|
26
|
+
import npguard as ng
|
|
27
|
+
|
|
28
|
+
with ng.memory_watcher("matrix_pipeline"):
|
|
29
|
+
a = np.random.rand(10_000, 100)
|
|
30
|
+
ng.register_array(a, "a")
|
|
31
|
+
|
|
32
|
+
b = a * 2 + a.mean(axis=0) - 1
|
|
33
|
+
ng.register_array(b, "b")
|
|
34
|
+
|
|
35
|
+
c = np.ascontiguousarray(b.T)
|
|
36
|
+
ng.register_array(c, "c")
|
|
37
|
+
|
|
38
|
+
ng.report()
|
|
39
|
+
ng.suggest()
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
npguard.py
|
|
3
|
+
|
|
4
|
+
NumPy Memory Guard (v1)
|
|
5
|
+
|
|
6
|
+
A NumPy memory observability and explanation tool.
|
|
7
|
+
|
|
8
|
+
Features:
|
|
9
|
+
1. Watch NumPy memory behavior
|
|
10
|
+
2. Notify users about memory pressure & temporaries
|
|
11
|
+
3. Suggest opt-in ways to reduce memory pressure
|
|
12
|
+
|
|
13
|
+
This module does NOT modify NumPy internals.
|
|
14
|
+
It only observes and explains memory behavior.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import tracemalloc
|
|
19
|
+
import gc
|
|
20
|
+
import inspect
|
|
21
|
+
from collections import defaultdict
|
|
22
|
+
from contextlib import contextmanager
|
|
23
|
+
|
|
24
|
+
# ---------------------------
|
|
25
|
+
# Global State (v1)
|
|
26
|
+
# ---------------------------
|
|
27
|
+
|
|
28
|
+
ArrayRegistry = {}
|
|
29
|
+
Alloc_Tracker = defaultdict(int)
|
|
30
|
+
_last_observation = {}
|
|
31
|
+
|
|
32
|
+
# ---------------------------
|
|
33
|
+
# Helpers
|
|
34
|
+
# ---------------------------
|
|
35
|
+
|
|
36
|
+
def array_size(arr: np.ndarray) -> int:
|
|
37
|
+
return arr.nbytes
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def register_array(arr: np.ndarray, label: str = "array"):
|
|
41
|
+
key = id(arr)
|
|
42
|
+
ArrayRegistry[key] = {
|
|
43
|
+
"label": label,
|
|
44
|
+
"size": arr.nbytes,
|
|
45
|
+
"shape": arr.shape,
|
|
46
|
+
"dtype": str(arr.dtype),
|
|
47
|
+
"owndata": arr.flags["OWNDATA"],
|
|
48
|
+
"contiguous": arr.flags["C_CONTIGUOUS"],
|
|
49
|
+
}
|
|
50
|
+
Alloc_Tracker[label] += arr.nbytes
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _cleanup_dead_arrays():
|
|
54
|
+
live_ids = {id(obj) for obj in gc.get_objects() if isinstance(obj, np.ndarray)}
|
|
55
|
+
dead = set(ArrayRegistry) - live_ids
|
|
56
|
+
for k in dead:
|
|
57
|
+
del ArrayRegistry[k]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _find_user_frame():
|
|
61
|
+
for frame_info in inspect.stack():
|
|
62
|
+
fname = frame_info.filename
|
|
63
|
+
if "npguard" not in fname and "contextlib" not in fname:
|
|
64
|
+
return frame_info
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------------------------
|
|
69
|
+
# Watcher (core feature)
|
|
70
|
+
# ---------------------------
|
|
71
|
+
|
|
72
|
+
@contextmanager
|
|
73
|
+
def memory_watcher(tag="block", warn_threshold_mb=10):
|
|
74
|
+
tracemalloc.start()
|
|
75
|
+
start_current, _ = tracemalloc.get_traced_memory()
|
|
76
|
+
start_snapshot = set(ArrayRegistry.keys())
|
|
77
|
+
|
|
78
|
+
yield
|
|
79
|
+
|
|
80
|
+
_cleanup_dead_arrays()
|
|
81
|
+
current, peak = tracemalloc.get_traced_memory()
|
|
82
|
+
tracemalloc.stop()
|
|
83
|
+
|
|
84
|
+
python_peak_mb = (peak - start_current) / 1024 / 1024
|
|
85
|
+
python_cur_mb = (current - start_current) / 1024 / 1024
|
|
86
|
+
|
|
87
|
+
new_arrays = len(set(ArrayRegistry.keys()) - start_snapshot)
|
|
88
|
+
|
|
89
|
+
numpy_live_mb = sum(info["size"] for info in ArrayRegistry.values()) / 1024 / 1024
|
|
90
|
+
temp_estimate_mb = max(0.0, python_peak_mb - numpy_live_mb)
|
|
91
|
+
|
|
92
|
+
_last_observation.update({
|
|
93
|
+
"tag": tag,
|
|
94
|
+
"python_peak_mb": python_peak_mb,
|
|
95
|
+
"python_current_mb": python_cur_mb,
|
|
96
|
+
"numpy_live_mb": numpy_live_mb,
|
|
97
|
+
"temp_estimate_mb": temp_estimate_mb,
|
|
98
|
+
"new_arrays": new_arrays,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
print(f"\n[npguard] Memory Watch: {tag}")
|
|
102
|
+
print(f" Python peak diff: {python_peak_mb:.2f} MB")
|
|
103
|
+
print(f" Python current diff: {python_cur_mb:.2f} MB")
|
|
104
|
+
print(f" NumPy live arrays: {numpy_live_mb:.2f} MB")
|
|
105
|
+
print(f" Estimated temporaries: {temp_estimate_mb:.2f} MB")
|
|
106
|
+
|
|
107
|
+
if python_peak_mb > warn_threshold_mb:
|
|
108
|
+
_emit_warning()
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------
|
|
112
|
+
# Warning & Explanation
|
|
113
|
+
# ---------------------------
|
|
114
|
+
|
|
115
|
+
def _emit_warning():
|
|
116
|
+
obs = _last_observation
|
|
117
|
+
frame = _find_user_frame()
|
|
118
|
+
location = f"{frame.filename}:{frame.lineno}" if frame else "<unknown>"
|
|
119
|
+
|
|
120
|
+
print("\n⚠️ Memory pressure detected")
|
|
121
|
+
print(f" Location: {location}")
|
|
122
|
+
print(f" Peak memory increase: {obs['python_peak_mb']:.2f} MB")
|
|
123
|
+
|
|
124
|
+
if obs["temp_estimate_mb"] > 5:
|
|
125
|
+
print(f" Likely temporary allocations: ~{obs['temp_estimate_mb']:.2f} MB")
|
|
126
|
+
print(" Cause: chained NumPy operations or broadcasting")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ---------------------------
|
|
130
|
+
# Suggestions (educational)
|
|
131
|
+
# ---------------------------
|
|
132
|
+
|
|
133
|
+
def _same_shape_groups():
|
|
134
|
+
groups = defaultdict(list)
|
|
135
|
+
for info in ArrayRegistry.values():
|
|
136
|
+
groups[(info["shape"], info["dtype"])].append(info)
|
|
137
|
+
return groups
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _forced_copy_detected():
|
|
141
|
+
for info in ArrayRegistry.values():
|
|
142
|
+
if info["owndata"] and not info["contiguous"]:
|
|
143
|
+
return True
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
def suggest(temp_threshold_mb=5):
|
|
147
|
+
print("\n[npguard] Suggestions:")
|
|
148
|
+
|
|
149
|
+
obs = _last_observation
|
|
150
|
+
if not obs:
|
|
151
|
+
print(" No observation data available.")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
temp_mb = obs["temp_estimate_mb"]
|
|
155
|
+
|
|
156
|
+
if temp_mb < temp_threshold_mb:
|
|
157
|
+
print(" No significant temporary allocations detected.")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
print(f" This block allocated ~{temp_mb:.2f} MB in temporary arrays.")
|
|
161
|
+
|
|
162
|
+
# 1 Chained expression detection
|
|
163
|
+
if temp_mb > obs["numpy_live_mb"] * 2:
|
|
164
|
+
print(
|
|
165
|
+
" • Likely cause: chained NumPy expressions creating intermediate arrays."
|
|
166
|
+
)
|
|
167
|
+
print(
|
|
168
|
+
" Suggestion: split expressions into steps or use ufuncs with `out=`."
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# 2️ Same-shape allocation reuse hint
|
|
172
|
+
shape_groups = _same_shape_groups()
|
|
173
|
+
for (shape, dtype), items in shape_groups.items():
|
|
174
|
+
if len(items) >= 2:
|
|
175
|
+
print(
|
|
176
|
+
f" • Detected {len(items)} arrays with same shape {shape} and dtype {dtype}."
|
|
177
|
+
)
|
|
178
|
+
print(
|
|
179
|
+
" Suggestion: consider reusing a preallocated buffer for these arrays."
|
|
180
|
+
)
|
|
181
|
+
break # avoid spam
|
|
182
|
+
|
|
183
|
+
# 3️ Forced copy hint
|
|
184
|
+
for info in ArrayRegistry.values():
|
|
185
|
+
if info["owndata"] and info["contiguous"]:
|
|
186
|
+
continue
|
|
187
|
+
if info["owndata"]:
|
|
188
|
+
print(
|
|
189
|
+
f" • Array '{info['label']}' appears to be a forced copy."
|
|
190
|
+
)
|
|
191
|
+
print(
|
|
192
|
+
" Suggestion: avoid operations that require contiguity (e.g. ascontiguousarray) if possible."
|
|
193
|
+
)
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
# ---------------------------
|
|
197
|
+
# Reporting
|
|
198
|
+
# ---------------------------
|
|
199
|
+
|
|
200
|
+
def report():
|
|
201
|
+
print("\n[npguard] Allocation Summary (cumulative)")
|
|
202
|
+
for label, size in Alloc_Tracker.items():
|
|
203
|
+
print(f" {label:<12}: {size/1024/1024:.2f} MB")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: npguard
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: NumPy memory observability and explanation tool
|
|
5
|
+
Author: Priyanshu Rauth
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/npguard
|
|
8
|
+
Project-URL: Issues, https://github.com/yourusername/npguard/issues
|
|
9
|
+
Keywords: numpy,memory,profiling,performance,scientific-computing
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering
|
|
13
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Requires-Python: >=3.8
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
Requires-Dist: numpy
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# npguard
|
|
24
|
+
|
|
25
|
+
**npguard** is a NumPy memory observability and explanation tool.
|
|
26
|
+
|
|
27
|
+
It helps developers understand *why* NumPy memory usage spikes by detecting
|
|
28
|
+
temporary allocations and explaining their causes, with safe, opt-in suggestions
|
|
29
|
+
to reduce memory pressure.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Watch NumPy-heavy code blocks
|
|
34
|
+
- Detect memory pressure and hidden temporaries
|
|
35
|
+
- Explain causes (chained ops, broadcasting)
|
|
36
|
+
- Provide safe optimization suggestions
|
|
37
|
+
- No monkey-patching, no unsafe automation
|
|
38
|
+
|
|
39
|
+
## What npguard does not do
|
|
40
|
+
- Does not modify NumPy behavior
|
|
41
|
+
- Does not automatically reuse buffers
|
|
42
|
+
- Does not rewrite code
|
|
43
|
+
|
|
44
|
+
## Example
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import numpy as np
|
|
48
|
+
import npguard as ng
|
|
49
|
+
|
|
50
|
+
with ng.memory_watcher("matrix_pipeline"):
|
|
51
|
+
a = np.random.rand(10_000, 100)
|
|
52
|
+
ng.register_array(a, "a")
|
|
53
|
+
|
|
54
|
+
b = a * 2 + a.mean(axis=0) - 1
|
|
55
|
+
ng.register_array(b, "b")
|
|
56
|
+
|
|
57
|
+
c = np.ascontiguousarray(b.T)
|
|
58
|
+
ng.register_array(c, "c")
|
|
59
|
+
|
|
60
|
+
ng.report()
|
|
61
|
+
ng.suggest()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
npguard
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "npguard"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "NumPy memory observability and explanation tool"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Priyanshu Rauth" }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
keywords = [
|
|
18
|
+
"numpy",
|
|
19
|
+
"memory",
|
|
20
|
+
"profiling",
|
|
21
|
+
"performance",
|
|
22
|
+
"scientific-computing"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 3 - Alpha",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Topic :: Scientific/Engineering",
|
|
29
|
+
"Topic :: Software Development :: Debuggers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3 :: Only"
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
dependencies = [
|
|
36
|
+
"numpy"
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/yourusername/npguard"
|
|
41
|
+
Issues = "https://github.com/yourusername/npguard/issues"
|
npguard-0.1.0/setup.cfg
ADDED