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 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()
@@ -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,13 @@
1
+ from .core import (
2
+ memory_watcher,
3
+ register_array,
4
+ report,
5
+ suggest,
6
+ )
7
+
8
+ __all__ = [
9
+ "memory_watcher",
10
+ "register_array",
11
+ "report",
12
+ "suggest",
13
+ ]
@@ -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,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ npguard/__init__.py
5
+ npguard/core.py
6
+ npguard.egg-info/PKG-INFO
7
+ npguard.egg-info/SOURCES.txt
8
+ npguard.egg-info/dependency_links.txt
9
+ npguard.egg-info/requires.txt
10
+ npguard.egg-info/top_level.txt
@@ -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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+