-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Improve allocation success when evicting from cache #6844
Conversation
✅ Deploy Preview for meta-velox canceled.
|
@oerling has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
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.
@oerling overall looks good to me. Thanks!
@@ -380,7 +384,14 @@ void CacheShard::evict(uint64_t bytesToFree, bool evictAllUnpinned) { | |||
continue; | |||
} | |||
largeFreed += candidate->data_.byteSize(); | |||
toFree.push_back(std::move(candidate->data())); | |||
if (acquirePages) { |
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.
if (acquiredPages > 0) {
if (canTryAllocate(numPages - collateral.numPages(), evicted)) { | ||
try { | ||
if (allocate(evicted)) { | ||
if (isCounted) { |
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.
Shall we just put isCounted is safe guard processing? Thanks!
if (isCounted) { | ||
--numThreadsInAllocate_; | ||
} | ||
return true; | ||
throw; |
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.
Shall we use rethrow here?
} | ||
++shardCounter_; | ||
int32_t toAcquire = |
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.
const int32_t numPagesToAcquire =
numPages * sizeMultiplier * memory::AllocationTraits::kPageSize, | ||
nthAttempt >= kNumShards); | ||
std::max<int32_t>(kMinEvictPages, numPages) * sizeMultiplier * | ||
memory::AllocationTraits::kPageSize, |
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.
Use memory::AllocationTraits::pageBytes()
auto candidatePages = candidate->data().numPages(); | ||
acquirePages = | ||
candidatePages > acquirePages ? 0 : acquirePages - candidatePages; | ||
allocation->appendMove(candidate->data()); |
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.
Add a check candidate is empty?
@@ -37,6 +37,14 @@ void Allocation::append(uint8_t* address, int32_t numPages) { | |||
"Appending a duplicate address into a PageRun"); | |||
runs_.emplace_back(address, numPages); | |||
} | |||
void Allocation::appendMove(Allocation& other) { |
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.
Can you add a unit test for this? Thanks!
@@ -185,9 +186,14 @@ bool MemoryAllocator::allocateContiguous( | |||
return allocateContiguousWithoutRetry( | |||
numPages, collateral, allocation, reservationCB, maxPages); | |||
} | |||
return cache()->makeSpace(numPages, [&]() { | |||
Allocation toFree; | |||
if (collateral) { |
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.
if (collateral != nullptr) {
@@ -198,7 +204,9 @@ bool MemoryAllocator::growContiguous( | |||
if (cache() == nullptr) { | |||
return growContiguousWithoutRetry(increment, allocation, reservationCB); | |||
} | |||
return cache()->makeSpace(increment, [&]() { | |||
Allocation empty; | |||
return cache()->makeSpace(increment, empty, [&](Allocation& evicted) { |
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.
Can we change collateral passed to makeSpace to a pointer? Thanks!
void evict( | ||
uint64_t bytesToFree, | ||
bool evictAllUnpinned, | ||
int32_t acquirePages = 0, |
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.
s/acquirePages/pagesToAcquire/
@oerling has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
@@ -706,7 +744,8 @@ CacheStats AsyncDataCache::refreshStats() const { | |||
|
|||
void AsyncDataCache::clear() { | |||
for (auto& shard : shards_) { | |||
shard->evict(std::numeric_limits<int32_t>::max(), true); | |||
memory::Allocation acquired; | |||
shard->evict(std::numeric_limits<int32_t>::max(), true, 0, acquired); |
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.
Put an assert acquired is empty?
@@ -548,8 +548,14 @@ class CacheShard { | |||
// not pinned. This favors first removing older and less frequently | |||
// used entries. If 'evictAllUnpinned' is true, anything that is | |||
// not pinned is evicted at first sight. This is for out of memory | |||
// emergencies. | |||
void evict(uint64_t bytesToFree, bool evictAllUnpinned); | |||
// emergencies. If 'acquireBytes' is set, up to this amount is added to |
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.
s/acquireBytes/pagesToAcquire/
virtual bool makeSpace( | ||
memory::MachinePageCount numPages, | ||
std::function<bool()> allocate) = 0; | ||
std::function<bool(Allocation& evicted)> allocate) = 0; |
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.
std::function<bool(Allocation&)>
@oerling has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
…r#6844) Summary: An allocation will evict cache to make space for data. If the the evicted data is freed and then an allocation is attempted, it can be that another thread has snatched the freed data. With high transient memory allocation rate and many threads, it can happen that a large allocation runs out of retries even if it could be satisfied. Therefore, when evicting in order to make space for an allocation, we keep hold of the non-contiguous Allocations we remove from cache. Many allocations can in this way be concatenated into an allocation that covers the needed number of pages. This can then atomically be converted into a new non-contiguous or contiguous allocation. The allocate*WithoutRetry methods accept a non-contiguous allocation to be freed to provide pages for the new allocation. Memory mapping magic can convert non-contiguous freed pages to contiguous ones. Note that the allocate*WithoutRetry methods mishandle the atomicity of the exchange: The allocated count is decremented by the size of the freed collateral and then incremented by the size of the new allocation. This should be a single atomic increment of allocatedSize - collateralSize. This always succeeds if the collateral is >= the allocated amount. Even so, the window of vulnerability to another thread snatching the freed capacity before it is reacquired is probably negligible, only a few instructions. The AsyncDataCache::makeSpace loop first tries to allocate. If it fails, it evicts and keeps up to the required amount in a a grab bag allocation. It loops until this allocation is large enough to convert into the desired allocation. If nothing is evicted and the allocation repeatedly fails, it gives up. Reviewed By: xiaoxmeng Differential Revision: D49829731 Pulled By: oerling
This pull request was exported from Phabricator. Differential Revision: D49829731 |
c828bc8
to
9e437f0
Compare
@oerling has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
@oerling has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
6ae77bb
to
f49b59d
Compare
@oerling has imported this pull request. If you are a Meta employee, you can view this diff on Phabricator. |
…r#6844) Summary: An allocation will evict cache to make space for data. If the the evicted data is freed and then an allocation is attempted, it can be that another thread has snatched the freed data. With high transient memory allocation rate and many threads, it can happen that a large allocation runs out of retries even if it could be satisfied. Therefore, when evicting in order to make space for an allocation, we keep hold of the non-contiguous Allocations we remove from cache. Many allocations can in this way be concatenated into an allocation that covers the needed number of pages. This can then atomically be converted into a new non-contiguous or contiguous allocation. The allocate*WithoutRetry methods accept a non-contiguous allocation to be freed to provide pages for the new allocation. Memory mapping magic can convert non-contiguous freed pages to contiguous ones. Note that the allocate*WithoutRetry methods mishandle the atomicity of the exchange: The allocated count is decremented by the size of the freed collateral and then incremented by the size of the new allocation. This should be a single atomic increment of allocatedSize - collateralSize. This always succeeds if the collateral is >= the allocated amount. Even so, the window of vulnerability to another thread snatching the freed capacity before it is reacquired is probably negligible, only a few instructions. The AsyncDataCache::makeSpace loop first tries to allocate. If it fails, it evicts and keeps up to the required amount in a a grab bag allocation. It loops until this allocation is large enough to convert into the desired allocation. If nothing is evicted and the allocation repeatedly fails, it gives up. Reviewed By: xiaoxmeng Differential Revision: D49829731 Pulled By: oerling
This pull request was exported from Phabricator. Differential Revision: D49829731 |
Conbench analyzed the 1 benchmark run on commit There were no benchmark performance regressions. 🎉 The full Conbench report has more details. |
…r#6844) Summary: An allocation will evict cache to make space for data. If the the evicted data is freed and then an allocation is attempted, it can be that another thread has snatched the freed data. With high transient memory allocation rate and many threads, it can happen that a large allocation runs out of retries even if it could be satisfied. Therefore, when evicting in order to make space for an allocation, we keep hold of the non-contiguous Allocations we remove from cache. Many allocations can in this way be concatenated into an allocation that covers the needed number of pages. This can then atomically be converted into a new non-contiguous or contiguous allocation. The allocate*WithoutRetry methods accept a non-contiguous allocation to be freed to provide pages for the new allocation. Memory mapping magic can convert non-contiguous freed pages to contiguous ones. Note that the allocate*WithoutRetry methods mishandle the atomicity of the exchange: The allocated count is decremented by the size of the freed collateral and then incremented by the size of the new allocation. This should be a single atomic increment of allocatedSize - collateralSize. This always succeeds if the collateral is >= the allocated amount. Even so, the window of vulnerability to another thread snatching the freed capacity before it is reacquired is probably negligible, only a few instructions. The AsyncDataCache::makeSpace loop first tries to allocate. If it fails, it evicts and keeps up to the required amount in a a grab bag allocation. It loops until this allocation is large enough to convert into the desired allocation. If nothing is evicted and the allocation repeatedly fails, it gives up. Pull Request resolved: facebookincubator#6844 Reviewed By: xiaoxmeng Differential Revision: D49829731 Pulled By: oerling fbshipit-source-id: ef5dae3fd6a35ea5eb2c81f41f41f2c41fc1fea2
An allocation will evict cache to make space for data. If the the evicted data is freed and then an allocation is attempted, it can be that another thread has snatched the freed data. With high transient memory allocation rate and many threads, it can happen that a large allocation runs out of retries even if it could be satisfied.
Therefore, when evicting in order to make space for an allocation, we keep hold of the non-contiguous Allocations we remove from cache. Many allocations can in this way be concatenated into an allocation that covers the needed number of pages. This can then atomically be converted into a new non-contiguous or contiguous allocation. The allocate*WithoutRetry methods accept a non-contiguous allocation to be freed to provide pages for the new allocation. Memory mapping magic can convert non-contiguous freed pages to contiguous ones.
Note that the allocate*WithoutRetry methods mishandle the atomicity of the exchange: The allocated count is decremented by the size of the freed collateral and then incremented by the size of the new allocation. This should be a single atomic increment of allocatedSize
The AsyncDataCache::makeSpace loop first tries to allocate. If it fails, it evicts and keeps up to the required amount in a a grab bag allocation. It loops until this allocation is large enough to convert into the desired allocation. If nothing is evicted and the allocation repeatedly fails, it gives up.