Skip to content

TaggedCache: Fix swept object memory release which can occur within the … #4242

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

Closed
wants to merge 3 commits into from

Conversation

greg7mdp
Copy link
Contributor

@greg7mdp greg7mdp commented Jul 18, 2022

…lock

High Level Overview of Change

When a std::shared_ptr is destructed, even if there is no other shared_ptr referencing the same object, the object memory is not necessarily released. This is because std::weak_ptr referencing the same object still need to reference the control block, and the control block is deleted only when the last std::weak_ptr is deleted.

When the object is allocated with std::make_shared, both the control block and the object are allocated within the same malloc block, and that malloc block is released only when no std::shared_ptr or std::weak_ptr reference it. Many commonly used objects are allocated with std::make_shared, including the SHAMap tree nodes.

As a result, the release of the memory used by many objects is still occurring within the lock in the TaggedCache (because it occurs when the std::weak_ptr is destructed).

Note: even prior to this change, the destructor for the shared objects is indeed called outside the lock, since the object destructor is called when the last strong reference (by shared_ptr) is destructed. It is only the actual memory release which occurred within the lock when the std::weak_ptr is destroyed.

Context of Change

This is a performance issue. It has very likely been present for a very long time.
I think this change should increase rippled's performance, as the TaggedCache lock will be held for a shorter time.

Type of Change

  • [ x] Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Tests (You added tests for code that already exists, or your new feature included in this PR)
  • Documentation Updates
  • Release

This change gathers both pointer types ( std::shared_ptr and std::weak_ptr) in vectors which are destroyed outside of the lock, ensuring that the memory deallocation occurs outside the lock, for both object memory and control block, regardless of whether they are allocated together or not.

I ran the unittests (no failure), and verified that rippled syncs correctly. In my opinion the change is simple and straightforward enough that it is unlikely to cause any problem.

@nbougalis
Copy link
Contributor

nbougalis commented Jul 18, 2022

I haven't reviewed this closely, but I think we need to clarify at least the comment wording and maybe the commit message:

An object pointed to via std::shared_ptr will go "out of scope" when the last strong pointer to the object is released and that object's destructor WILL be called at that time.

Now, the underlying memory may not be released until the last weak pointer to the object goes away since, as you say, a std::weak_ptr to an object needs to reference the control block. So if a control block and the object where allocated in one go (e.g. via std::make_shared) the memory can only be released in one go as well.

@greg7mdp greg7mdp changed the title TaggedCache: Fix swept object destruction which can occur within the … TaggedCache: Fix swept object memory release which can occur within the … Jul 18, 2022
@greg7mdp
Copy link
Contributor Author

Yes @nbougalis you are 100% correct, thanks for clarifying, I have updated the PR description.

Copy link
Contributor

@nbougalis nbougalis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

We'll need to fold these two commits into one and adjust the commit message during merge. You can do it now if you prefer, @greg7mdp.

@greg7mdp
Copy link
Contributor Author

Thanks @nbougalis I merged the commits and updated the message.

Copy link
Collaborator

@mtrippled mtrippled left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 LGTM

@mtrippled
Copy link
Collaborator

mtrippled commented Jul 27, 2022 via email

@HowardHinnant
Copy link
Contributor

Yes, in C++17 and later, because it is in the C++ language.

@HowardHinnant
Copy link
Contributor

pre-C++17 I would actually prefer:

(void)foo;

over:

boost::ignore_unused(foo);

The void solution is a decades old pattern that is well understood and doesn't depend on a library. I don't know what problem boost::ignore_unused solves.

Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the suggestions below, there's a typo in the first commit message. "objet" should be "object". I would also suggest rewriting it as "Release TaggedCache object memory outside the lock". (Per https://cbea.ms/git-commit/, leave the period off the end, too.)

@greg7mdp
Copy link
Contributor Author

@HowardHinnant @nbougalis @ximinez @mtrippled I think I would like to abandon this PR.
Because of the effort that was made to release the SHAMAPTreeNodes outside the lock, I was under the impression that holding the cache lock for the minimum time possible was of the utmost importance, and that is why I proposed this change. It seemed like a good idea, but I did not test whether it the change had any impact on performance.

However, after submitting the PR, I experimented with replacing the hardened_partitioned_hash_map with another hash map of mine which doesn't hold a global lock, but a lock for each partition (basically this commit ), and this should have eliminated most locking, but the time for rippled to sync increased slightly from 4 minutes to almost 5 minutes (maybe this is not the right benchmark), so I am not sure anymore that reducing slightly the locking duration is critical.

Please let me know what you guys think. Thanks!

@mtrippled
Copy link
Collaborator

@greg7mdp I think that this improvement is beneficial, but probably not measurably so. It can't hurt--I always assumed that an expired weak_ptr had already been destroyed, but I guess that's not necessarily the case. I don't see a problem with keeping this, and I've already approved it.

@mtrippled
Copy link
Collaborator

Also, @greg7mdp you can measure the impact of your change with normal operation by grepping for "sweep lock duration" from the debug log. That measures the duration between the first partition lock being acquired and the last partition lock being freed. There are several caches like this, but the worst one is the "tree node cache". Any decrease in lock duration is a good thing, and this is a way to easily measure any changes.

@ximinez
Copy link
Collaborator

ximinez commented Aug 25, 2022

I agree with @mtrippled. I don't think this should be abandoned unless the sweep messages indicate degraded performance.

@greg7mdp
Copy link
Contributor Author

OK, fair enough and thanks @mtrippled and @ximinez , I'll make the requested changes.

@ximinez ximinez added the Ready to merge *PR author* thinks it's ready to merge. Has passed code review. Perf sign-off may still be required. label Aug 29, 2022
@manojsdoshi manojsdoshi mentioned this pull request Oct 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Ready to merge *PR author* thinks it's ready to merge. Has passed code review. Perf sign-off may still be required.
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

5 participants