mspass-team / mspass-team/mspass_tutorial
ensemble member vector hogging memory
Nobody has claimed this yet.
- Dominant language
- Jupyter Notebook
- Stars
- 9
- Forks
- 8
- PR merge metrics
- No merged PRs in 30d
Description
This is a solved problem, but I'm pushing it as an issue to add text to address the fundamental problem it defines. We need to develop guidance to avoid this problem.
The problem is defined by this function used in a workflow I developed for extracting segmented, event waveforms from continuous data. This was my original function that seemed to create a memory problem:
def merge_ensembles(enslist,yearday_key="jday_tag")->tuple:
"""
Does an operation a bit like Undertaker.bring_out_your dead.
That is it returns a tuple with 0 containing a single TimeSeriesEnsemble
of all live data and 1 containing a list of documents of read failures.
It differs from bring_out_your_dead because input, enslist, is a list of
ensembles - some of which are good and some of which are failures.
"""
ens_merged = TimeSeriesEnsemble()
failures = list()
for ens in enslist:
if ens.live:
if yearday_key not in ens_merged:
if yearday_key in ens:
ens_merged[yearday_key] = ens[yearday_key]
for d in ens.member:
ens_merged.member.append(d)
else:
# let this throw an exception if the required metadata are missing
# that shouldn't happen since this is internal-beware if this is copied
doc = dict(ens)
elogdoc = elog2doc(ens.elog)
doc['elog_content'] = elogdoc
failures.append(doc)
if len(ens_merged)>0:
ens_merged.set_live()
return [ens_merged,failures]
It was used in a completion function run with sliding_window_pipeline as follows:
def save_jday_outputs(reader_output,
dbname_or_handle,
s3fshandle,
bucket,
year,
session=None,
yearday_key="jday_tag",
s3_fail_collection="s3_read_failures",
verbose=False,
)->list:
"""
Saves the segments extracted from day files with the get_s3_segments function
as pickle files in the S3 bucket pointed to by s3fshandle.
Any ensembles marked dead will be handled specially with the function
bury_s3_read_failures.
"""
# put here as this is a common print message for any write errors
write_message0 = f"save_jday_outputs (WARNING): failed trying to send serialized data to s3 bucket={bucket}\n"
s3_client = fetch_s3_client(session)
db = fetch_dbhandle(dbname_or_handle)
# first clean the inputs to separate the failures from the live data
ens,failures = merge_ensembles(reader_output)
del reader_output
if len(failures)>0:
db[s3_fail_collection].insert_many(failures)
nfailed=len(failures)
del failures
if ens.dead():
# if the ensemble is dead here it is empty so no reason to continue
return False
if yearday_key in ens:
yrdaystr = ens[yearday_key]
objname_base = yrdaystr
else:
objname_base = str(ObjectId())
print(f"save_segment_reader (WARNING): ensemble Metadata is missing yearday_key={yearday_key}\n")
print("Using unique id string generated wity pymongo ObjectId=",objname_base)
s3object_key = f"{bucket}/{year}/{objname_base}.pickle"
if verbose:
print("Saving ",len(ens.member)," ensembles to ",s3object_key," Number of failures=",nfailed)
try:
with s3fshandle.open(s3object_key,"wb") as fh:
pickle.dump(ens,fh)
del ens
return True
except Exception as e:
print("Failed to write ",s3object_key)
print(e)
del ens
return False
A solution suggested by an AI that seems to work is to implement merge_enembles as follows:
def merge_ensembles(enslist, yearday_key="jday_tag"):
"""Merge in order and consume the input list, replacing entries with None.
Called only for a disposable worker result. A Python parameter deletion
cannot release the pipeline's reference to that list; clearing its entries
releases source ensembles incrementally. Members are still copied into the
output, but the complete input need not coexist with serialization state.
"""
count = sum(len(ens.member) for ens in enslist if ens.live)
merged = TimeSeriesEnsemble(count)
failures = []
for index in range(len(enslist)):
ens = enslist[index]
try:
if ens.live:
if yearday_key not in merged and yearday_key in ens:
merged[yearday_key] = ens[yearday_key]
for member in ens.member:
merged.member.append(member)
# A pybind member wrapper can keep its source vector alive.
if len(ens.member):
del member
else:
document = dict(ens)
document["elog_content"] = elog2doc(ens.elog)
failures.append(document)
finally:
enslist[index] = None
del ens
if len(merged.member):
merged.set_live()
return [merged, failures]
It left save_jday_outputs largely the same but it removed the explicit del ens commands in that function.
Long background but the question is why the AI solution works but the original causes a memory bloat? The AIs typically obscure comments suggest some fundamental problem with you pybind11 handles binding std::vector containers to python. Namely "A pybind11 member wrapper can keep its source vector alive." Anyone have clue what that means? Makes no sense to me that deleting the members is necessary. What I did originally seems cleaner which was delete the entire list (enslist in the function arg) after the code was done with it. Why does the AI code not cause bloat but the other did?
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Review the merge_ensembles and save_jday_outputs examples in the issue, including how the disposable worker result is handled. The documentation is done when it gives clear guidance on the reported memory behavior and explains why deleting list entries differs from deleting the local list reference, without requiring readers to infer the rationale from the example.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- documentation
- Issue type
- Documentation
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 45/100