Skip to content
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

timely-util: implement an accounting container builder #27811

Merged
merged 2 commits into from
Jun 24, 2024

Conversation

petrosagg
Copy link
Contributor

Motivation

This PR adds a container builder wrapper that keeps track of the heap allocated bytes produced so far. This information can be used by operators to decide when it is a good time to yield.

Tips for reviewer

Checklist

@petrosagg petrosagg requested a review from a team as a code owner June 22, 2024 16:56
@petrosagg petrosagg requested a review from antiguru June 22, 2024 16:56
let local = Vec::with_capacity((capacity + Self::CHUNK - 1) / Self::CHUNK);
let chunks = (capacity + Self::CHUNK - 1) / Self::CHUNK;
let mut local = vec![];
local.resize_with(chunks, || Array::with_capacity(Self::CHUNK));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@antiguru the code here was not actually reserving the size in the arrays so I'm doing that here. I have updated the push method to look at capacity to decide whether a new chunk is needed. This allows us to also reserve space from SizableContainer::reserve

Copy link
Member

Choose a reason for hiding this comment

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

It was somewhat intentional to not preallocate the arrays. The cost of getting an array right now vs later should be the same, and getting it later opens up the opportunity to recycle allocations more eagerly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm, that's surprising! Isn't the purpose of a with_capacity API to ensure that I can insert X items without extra allocations? I can certainly revert this and make the reserve function a no-op (i.e just add capacity to hold the number of chunks needed) but that seems like the wrong implementation.

Regarding cost, while there are no reallocations of the actual data here there is potentially temporal cost because work that can be done now is deferred for the future and now might be a more convenient time.

In general, with a with_capacity API all these considerations can be thought of at the calling site by using a capacity of 0 or the actual value depending on when they want the allocations to happen.

Copy link
Member

Choose a reason for hiding this comment

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

I would say it's because with_capacity is a under defined API. We have to support it as it's part of the interface, and at the same time this implementation knows more about itself than the caller. At the same time, it might be the best we can offer.

I think it's very important that we do not preallocate: imagine we're forming a new largest batch in a spine, and constructing it takes a long time. We want to minimize the resource footprint during construction to avoid running out of resources. Additionally, differential chooses a capacity of the sum of input sizes, because it needs to assume that nothing will cancel. Often, we end with less than the sum, and we don't want to be in a position to carry unused memory with us, or needing to reallocate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We want to minimize the resource footprint during construction to avoid running out of resources

Additionally, differential chooses a capacity of the sum of input sizes

I'm still a bit confused because these two seem contradictory. If DD doesn't want preallocation to happen why call with_capacity with a large value? It sounds like DD needs to use with_capacity(0) and everything will work out fine when with_capacity does what it says on the tin. What am I missing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I reverted to the previous capacity behavior to unblock this PR but something doesn't feel right with how DD is using this API

Signed-off-by: Petros Angelatos <petrosagg@gmail.com>
Signed-off-by: Petros Angelatos <petrosagg@gmail.com>
}

fn preferred_capacity() -> usize {
if ENABLE_CHUNKED_STACK.load(std::sync::atomic::Ordering::Relaxed) {
Copy link
Member

Choose a reason for hiding this comment

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

You or I should check whether the two branches return different values. I think they should not because changing the preferred capacity at run time is a case I didn't think about and can cause undesired effects.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They both end up in timely_container::buffer::preferred_capacity

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I've intended to make this a stronger statement: It should just call timely::container::buffer::preferred_capacity to protect against someone changing the preferred capacity and suddenly things starting to crash/oom. preferred_capacity should probably be a const fn.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries, I will put up a new PR to fix this

@petrosagg petrosagg merged commit 448764f into MaterializeInc:main Jun 24, 2024
76 checks passed
@petrosagg petrosagg deleted the handle-accounting branch June 24, 2024 10:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants