8000 MAINT: Improve memory usage in PEP3118 format parsing · numpy/numpy@e0fb54f · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit e0fb54f

Browse files
committed
MAINT: Improve memory usage in PEP3118 format parsing
Previously a local `Stream` class would be defined every time a format needed parsing. Classes in cpython create reference cycles, which create load on the GC. This may or may not resolve gh-6511
1 parent a52634d commit e0fb54f

File tree

2 files changed

+53
-33
lines changed

2 files changed

+53
-33
lines changed

numpy/core/_internal.py

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -444,46 +444,46 @@ def _view_is_safe(oldtype, newtype):
444444
}
445445
_pep3118_standard_typechars = ''.join(_pep3118_standard_map.keys())
446446

447-
def _dtype_from_pep3118(spec):
448447

449-
class Stream(object):
450-
def __init__(self, s):
451-
self.s = s
452-
self.byteorder = '@'
448+
class _Stream(object):
449+
def __init__(self, s):
450+
self.s = s
451+
self.byteorder = '@'
453452

454-
def advance(self, n):
455-
res = self.s[:n]
456-
self.s = self.s[n:]
457-
return res
453+
def advance(self, n):
454+
res = self.s[:n]
455+
self.s = self.s[n:]
456+
return res
458457

459-
def consume(self, c):
460-
if self.s[:len(c)] == c:
461-
self.advance(len(c))
462-
return True
463-
return False
464-
465-
def consume_until(self, c):
466-
if callable(c):
467-
i = 0
468-
while i < len(self.s) and not c(self.s[i]):
469-
i = i + 1
470-
return self.advance(i)
471-
else:
472-
i = self.s.index(c)
473-
res = self.advance(i)
474-
self.advance(len(c))
475-
return res
458+
def consume(self, c):
459+
if self.s[:len(c)] == c:
460+
self.advance(len(c))
461+
return True
462+
return False
476463

477-
@property
478-
def next(self):
479-
return self.s[0]
464+
def consume_until(self, c):
465+
if callable(c):
466+
i = 0
467+
while i < len(self.s) and not c(self.s[i]):
468+
i = i + 1
469+
return self.advance(i)
470+
else:
471+
i = self.s.index(c)
472+
res = self.advance(i)
473+
self.advance(len(c))
474+
return res
480475

481-
def __bool__(self):
482-
return bool(self.s)
483-
__nonzero__ = __bool__
476+
@property
477+
def next(self):
478+
return self.s[0]
484479

485-
stream = Stream(spec)
480+
def __bool__(self):
481+
return bool(self.s)
482+
__nonzero__ = __bool__
486483

484+
485+
def _dtype_from_pep3118(spec):
486+
stream = _Stream(spec)
487487
dtype, align = __dtype_from_pep3118(stream, is_subdtype=False)
488488
return dtype
489489

numpy/tests/test_ctypeslib.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,23 @@ def check(x):
170170
check(as_array(pointer(c_array), shape=()))
171171
check(as_array(pointer(c_array[0]), shape=(2,)))
172172
check(as_array(pointer(c_array[0][0]), shape=(2, 3)))
173+
174+
def test_reference_cycles(self):
175+
# related to gh-6511
176+
import ctypes
177+
178+
# create array to work with
179+
# don't use int/long to avoid running into bpo-10746
180+
N = 100
181+
a = np.arange(N, dtype=np.short)
182+
183+
# get pointer to array
184+
pnt = np.ctypeslib.as_ctypes(a)
185+
186+
with np.testing.assert_no_gc_cycles():
187+
# decay the array above to a pointer to its first element
188+
newpnt = ctypes.cast(pnt, ctypes.POINTER(ctypes.c_short))
189+
# and construct an array using this data
190+
b = np.ctypeslib.as_array(newpnt, (N,))
191+
# now delete both, which should cleanup both objects
192+
del newpnt, b

0 commit comments

Comments
 (0)
0