-
Notifications
You must be signed in to change notification settings - Fork 177
Remove reference cycle in VecAccessMixin #4033
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
Conversation
|
|
connorjward
left a comment
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.
This is an excellent spot!
I have absolutely no idea why this is failing tests though... AFAICT the changes you have made shouldn't impact the rest of the code.
97b5063 to
2af3ac7
Compare
|
I had to do a few wider modifications around the inheritance of |
afe09ea to
5edefe0
Compare
With an associated PETSc Vec, VecAccessMixin deferred its version property to a lambda to avoid allocating the storage until necessary. Unfortunately, this lambda creates a reference cycle to self for all users of the VecAccessMixin. Given that counter accesses should be relatively infrequent, it seems fine to look up the counter type within the method itself.
Doesn't make sense to cache a reference to self, just return self.
The inheritance chain for Dat and Global puts VecAccessMixin (rightly) behind DataCarrier. This means that by MRO, the increment_dat_version method provided on DataCarrier will be used, which is a null operation. I think this makes sense, given that not all classes use this. However, because we're providing increment_dat_version as an override through VecAccessMixin, we need to explicitly refer to it in the inheriting classes.
5edefe0 to
bd2f8ad
Compare
|
connorjward
left a comment
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.
Sorry for taking so long to review this. I think that your changes demonstrate that VecAccessMixin is a poor class for this as some things have a dat_version but no Vec.
Otherwise updating a dat through a view doesn't increment the version. Co-authored-by: Connor Ward <[email protected]>
* Remove reference cycle in VecAccessMixin With an associated PETSc Vec, VecAccessMixin deferred its version property to a lambda to avoid allocating the storage until necessary. Unfortunately, this lambda creates a reference cycle to self for all users of the VecAccessMixin. Given that counter accesses should be relatively infrequent, it seems fine to look up the counter type within the method itself. * Don't cache topological on CoordinatelessFunction Doesn't make sense to cache a reference to self, just return self. * Convert increment_dat_version to abstract on DataCarrier The inheritance chain for Dat and Global puts VecAccessMixin (rightly) behind DataCarrier. This means that by MRO, the increment_dat_version method provided on DataCarrier will be used, which is a null operation. I think this makes sense, given that not all classes use this. However, because we're providing increment_dat_version as an override through VecAccessMixin, we need to explicitly refer to it in the inheriting classes. * Update pyop2/types/dat.py Otherwise updating a dat through a view doesn't increment the version. Co-authored-by: Connor Ward <[email protected]> --------- Co-authored-by: David A. Ham <[email protected]> Co-authored-by: Connor Ward <[email protected]>
With an associated PETSc Vec, VecAccessMixin deferred its version property to a lambda to avoid allocating the storage until necessary. Unfortunately, this lambda creates a reference cycle to self for all users of the VecAccessMixin. Given that counter accesses should be relatively infrequent, it seems fine to look up the counter type within the method itself.
Description
Related to #4014. To benchmark, I'm using the following script (very similar to the one in the linked issue, but uses 500 timesteps, a timestepper object, and removes explicit GC calls):
I'm also running this on the #4020 branch to automatically enable the
SingleDiskStorageScheduleand handle the leak offunctionwithinCheckpointFunction. On the pyadjoint side, I am using dolfin-adjoint/pyadjoint#194.Here's a pretty simple

mprofcomparison:In black is the base, without this branch. In blue is the base, but with
gc.collect()withinBlock.recompute(very eager, and expensive, also doesn't apply to the derivative). In red is the result with this branch, without any explicit gc. Individual plots follow, but the rescaling means you have to look a bit more closely.Base plot
GC plot
This PR
I think there is a still a bit left out there in terms of making expensive allocations delete through refcounting, and perhaps there's a more elegant way of implementing the change proposed here.