-
Notifications
You must be signed in to change notification settings - Fork 263
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
Implement lru_cache for get_variables_by_attributes #1253
Implement lru_cache for get_variables_by_attributes #1253
Conversation
My changes caused the error in the serial variant but I'm not sure how to fix it.
|
Maybe clear the LRU cache in the |
ad8df0a
to
b739ccc
Compare
# close file when there are no references to object left | ||
# close file when there are no references to object left and clear the cache. | ||
if self.get_variables_by_attributes: | ||
self.get_variables_by_attributes.cache_clear() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we do need this but this alone did not fix the failing test. In fact, the __dealloc__
test fails when I add a function object in the class __init__
that is a reference to the def
ined one, no relation to caching or not. I don't know enough about cython to understand what is wrong there.
@@ -3333,9 +3336,10 @@ of existing (sub-) groups and their variables. | |||
for group in groups: | |||
group.set_ncstring_attrs(value) # recurse into subgroups... | |||
|
|||
@functools.lru_cache(maxsize=128) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the decorator here makes things simpler but can lead to memory leaks, see python/cpython#64058. However, b/c I could not make the most common fix for this work here and I believe with the cache clear in the __dealloc__
we cover the cases of opening and closing multiple datasets, I don't think this would be a problem. (I may be wrong here! I'm not an expert and I'm envisioning a subset of the use cases I know.)
What do you think @jswhit?
The alternative would be to factor this function out into a private one that takes self.variables
and self._vars
, cache that one, and use it in the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ocefpaf I'm OK with this approach unless there are significant memory leaks. I guess you could create a test for memory leaks by repeatedly opening and closing the same file and invoking get_variables_by_attributes multiple times to see what happens to memory usage.
1618ea5
to
91af6c5
Compare
2512ed3
to
412fbb3
Compare
@@ -0,0 +1,52 @@ | |||
import os | |||
import tracemalloc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if I'm using this correctly. I'm out of my depth here. Also, it fails in the CIs but works locally. Not sure why.
Edit: moving it before the __main__
works and the reported memory seems OK.
test/tst_multiple_open_close.py
Outdated
netcdf_file = os.path.join(os.path.dirname(__file__), "netcdf_dummy_file.nc") | ||
snapshot = tracemalloc.take_snapshot() | ||
|
||
# k_times = 1000_000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We definitely don't want a large number on the CIs, or maybe we don't even want this test to be run. With that said, I ran this locally with 1000_000
and it worked out fine. Here are the stats for it:
/home/filipe/Dropbox/pymodules/01-forks/netcdf4-python/test/tst_multiple_open_close.py:0: size=1555 KiB (+1554 KiB), count=15380 (+15377), average=104 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/argparse.py:0: size=12.7 KiB (-4240 B), count=123 (-44), average=105 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/tracemalloc.py:0: size=896 B (+840 B), count=9 (+8), average=100 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/re/_compiler.py:0: size=1448 B (-224 B), count=5 (-4), average=290 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/contextlib.py:0: size=592 B (-128 B), count=3 (-2), average=197 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/re/_parser.py:0: size=0 B (-112 B), count=0 (-2)
<frozen posixpath>:0: size=129 B (-96 B), count=1 (-2), average=129 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/unittest/case.py:0: size=864 B (-64 B), count=7 (-1), average=123 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/unittest/main.py:0: size=1140 B (+0 B), count=9 (+0), average=127 B
/home/filipe/micromamba/envs/NETCDF4/lib/python3.11/unittest/runner.py:0: size=1088 B (+0 B), count=8 (+0), average=136 B
It doesn't seem to produce a leak.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to run this on the CIs - it's enough to know that there is no significant leak now. Thanks for creating that test, will be very useful to have. I think this is now ready to merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to keep this test but not run it? Or should I just remove it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep the test but just don't run it
test/run_all.py
Outdated
@@ -57,6 +57,11 @@ | |||
test_files.remove('tst_cdl.py'); | |||
sys.stdout.write('not running tst_cdl.py ...\n') | |||
|
|||
# Don't run computationally intensive test | |||
if os.getenv('MEMORY_LEAK_TEST'): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want if not os.getenv('MEMORY_LEAK_TEST'):
so the test doesn't run unless the env var is set? Looks like the test did run (and pass) in the latest github actions.
Still a work in progress and should be reviewed after #1252.
I'm not sure this is the best approach b/c the
Dataset
class will get a nice cached version of it without the memory leak caused if we use the decorator directly. However, that means theMFDataset
will get the uncached one. We can do the same we did forDataset
in this PR to implement a cached version forMFDataset
but I'm not sure that is worth the extra code (burden).