-
Notifications
You must be signed in to change notification settings - Fork 5.5k
stats: Improve performance of clearing scopes and histograms by batching them #15876
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
Changes from 5 commits
c6c2a87
02eec84
b9d7e90
d404db3
a7f5658
c29dc05
aedf7bd
6325e98
c3b6d53
001d5f6
a488f0a
42a504b
e6aa9c2
6b34a72
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,9 @@ ThreadLocalStoreImpl::~ThreadLocalStoreImpl() { | |
| ASSERT(shutting_down_ || !threading_ever_initialized_); | ||
| default_scope_.reset(); | ||
| ASSERT(scopes_.empty()); | ||
| ASSERT(scopes_to_cleanup_.empty()); | ||
| ASSERT(central_cache_entries_to_cleanup_.empty()); | ||
| ASSERT(histograms_to_cleanup_.empty()); | ||
| } | ||
|
|
||
| void ThreadLocalStoreImpl::setHistogramSettings(HistogramSettingsConstPtr&& histogram_settings) { | ||
|
|
@@ -199,6 +202,15 @@ void ThreadLocalStoreImpl::initializeThreading(Event::Dispatcher& main_thread_di | |
| void ThreadLocalStoreImpl::shutdownThreading() { | ||
| // This will block both future cache fills as well as cache flushes. | ||
| shutting_down_ = true; | ||
|
|
||
| // We can't call runOnAllThreads here as global threading has already been shutdown. | ||
|
jmarantz marked this conversation as resolved.
Outdated
|
||
| // It is okay to simply clear the scopes and central cache entries to cleanup. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you include an explanation for why this is okay to do?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will update. How is: |
||
| { | ||
| Thread::LockGuard lock(lock_); | ||
| scopes_to_cleanup_.clear(); | ||
| central_cache_entries_to_cleanup_.clear(); | ||
| } | ||
|
|
||
| Thread::LockGuard lock(hist_mutex_); | ||
| for (ParentHistogramImpl* histogram : histogram_set_) { | ||
| histogram->setShuttingDown(true); | ||
|
|
@@ -261,31 +273,47 @@ void ThreadLocalStoreImpl::releaseScopeCrossThread(ScopeImpl* scope) { | |
| // | ||
| // Since this is called from ScopeImpl's destructor, we must bump the | ||
| // ref-count of the central-cache by copying to a local scoped pointer, and | ||
| // keep that reference alive until all the TLS caches are clear. | ||
| CentralCacheEntrySharedPtr central_cache = scope->central_cache_; | ||
| // keep that reference alive until all the TLS caches are clear. This is done by keeping a | ||
| // separate vector of shared_ptrs which will be destructed once all threads have completed. | ||
|
|
||
| // This can happen from any thread. We post() back to the main thread which will initiate the | ||
| // cache flush operation. | ||
| if (!shutting_down_ && main_thread_dispatcher_) { | ||
| const uint64_t scope_id = scope->scope_id_; | ||
| // Switch to batching of clearing scopes. This greatly reduces the overhead when there are | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: s/Switch to batching of clearing scopes/Clear scopes in a batch/ as the reader of this won't be anchoring on the previous implementation.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will change |
||
| // tens of thousands of scopes to clear in a short period. i.e.: VHDS updates with tens of | ||
| // thousands of VirtualHosts | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: end sentences with period.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will fix |
||
| bool need_post = scopes_to_cleanup_.empty(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mind including an explanation why we post when this is empty? I assume this is the batching mechanism so that we don't post while there is a post in progress, but some detail would be nice to have here
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. will do.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. updating releaseScopesCrossThread to have some of the same info as releaseHistogramCrossThread. This should cover this and the additional comment below. :) |
||
| scopes_to_cleanup_.push_back(scope->scope_id_); | ||
| central_cache_entries_to_cleanup_.push_back(scope->central_cache_); | ||
| lock.release(); | ||
|
|
||
| // TODO(jmarantz): consider batching all the scope IDs that should be | ||
| // cleared from TLS caches to reduce bursts of runOnAllThreads on a large | ||
| // config update. See the pattern below used for histograms. | ||
| main_thread_dispatcher_->post([this, central_cache, scope_id]() { | ||
| sync_.syncPoint(MainDispatcherCleanupSync); | ||
| clearScopeFromCaches(scope_id, central_cache); | ||
| }); | ||
| if (need_post) { | ||
| main_thread_dispatcher_->post([this]() { | ||
| sync_.syncPoint(MainDispatcherCleanupSync); | ||
| clearScopesFromCaches(); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void ThreadLocalStoreImpl::releaseHistogramCrossThread(uint64_t histogram_id) { | ||
| // This can happen from any thread. We post() back to the main thread which will initiate the | ||
| // cache flush operation. | ||
| if (!shutting_down_ && main_thread_dispatcher_) { | ||
| main_thread_dispatcher_->post( | ||
| [this, histogram_id]() { clearHistogramFromCaches(histogram_id); }); | ||
| // It's possible that many different histograms will be deleted at the same | ||
| // time, before the main thread gets a chance to run | ||
| // clearHistogramsFromCaches. If a new histogram is deleted before that | ||
| // post runs, we add it to our list of histograms to clear, and there's no | ||
| // need to issue another post. | ||
|
Comment on lines
+311
to
+313
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see this is explained here, would be good to have this explanation for the above scope release :) |
||
| bool need_post = false; | ||
| { | ||
| Thread::LockGuard lock(hist_mutex_); | ||
| need_post = histograms_to_cleanup_.empty(); | ||
| histograms_to_cleanup_.push_back(histogram_id); | ||
| } | ||
| if (need_post) { | ||
| main_thread_dispatcher_->post([this]() { clearHistogramsFromCaches(); }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -294,39 +322,63 @@ ThreadLocalStoreImpl::TlsCache::insertScope(uint64_t scope_id) { | |
| return scope_cache_[scope_id]; | ||
| } | ||
|
|
||
| void ThreadLocalStoreImpl::TlsCache::eraseScope(uint64_t scope_id) { scope_cache_.erase(scope_id); } | ||
| void ThreadLocalStoreImpl::TlsCache::eraseHistogram(uint64_t histogram_id) { | ||
| void ThreadLocalStoreImpl::TlsCache::eraseScopes(const std::vector<uint64_t>& scope_ids) { | ||
| for (uint64_t scope_id : scope_ids) { | ||
| scope_cache_.erase(scope_id); | ||
| } | ||
| } | ||
|
|
||
| void ThreadLocalStoreImpl::TlsCache::eraseHistograms(const std::vector<uint64_t>& histograms) { | ||
| // This is called for every histogram in every thread, even though the | ||
| // histogram may not have been cached in each thread yet. So we don't | ||
| // want to check whether the erase() call erased anything. | ||
| tls_histogram_cache_.erase(histogram_id); | ||
| for (uint64_t histogram_id : histograms) { | ||
| tls_histogram_cache_.erase(histogram_id); | ||
| } | ||
| } | ||
|
|
||
| void ThreadLocalStoreImpl::clearScopeFromCaches(uint64_t scope_id, | ||
| CentralCacheEntrySharedPtr central_cache) { | ||
| void ThreadLocalStoreImpl::clearScopesFromCaches() { | ||
| // If we are shutting down we no longer perform cache flushes as workers may be shutting down | ||
| // at the same time. | ||
| if (!shutting_down_) { | ||
| // Perform a cache flush on all threads. | ||
|
|
||
| // Capture all the pending scope ids in a local, clearing the list held in | ||
| // this. Once this occurs, if a new scope is deleted, a new post will be | ||
| // required. | ||
| auto scope_ids = std::make_shared<std::vector<uint64_t>>(); | ||
| // Capture all the central cache entries for scopes we're deleting. These will be freed after | ||
| // all threads have completed. | ||
| auto central_caches = std::make_shared<std::vector<CentralCacheEntrySharedPtr>>(); | ||
| { | ||
| Thread::LockGuard lock(lock_); | ||
| *scope_ids = std::move(scopes_to_cleanup_); | ||
| scopes_to_cleanup_.clear(); | ||
| *central_caches = std::move(central_cache_entries_to_cleanup_); | ||
| central_cache_entries_to_cleanup_.clear(); | ||
| } | ||
|
|
||
| tls_cache_->runOnAllThreads( | ||
| [scope_id](OptRef<TlsCache> tls_cache) { tls_cache->eraseScope(scope_id); }, | ||
| [central_cache]() { /* Holds onto central_cache until all tls caches are clear */ }); | ||
| [scope_ids](OptRef<TlsCache> tls_cache) { tls_cache->eraseScopes(*scope_ids); }, | ||
| [central_caches]() { /* Holds onto central_caches until all tls caches are clear */ }); | ||
| } | ||
| } | ||
|
|
||
| void ThreadLocalStoreImpl::clearHistogramFromCaches(uint64_t histogram_id) { | ||
| void ThreadLocalStoreImpl::clearHistogramsFromCaches() { | ||
| // If we are shutting down we no longer perform cache flushes as workers may be shutting down | ||
| // at the same time. | ||
| if (!shutting_down_) { | ||
| // Perform a cache flush on all threads. | ||
| // | ||
| // TODO(jmarantz): If this cross-thread posting proves to be a performance | ||
| // bottleneck, | ||
| // https://gist.github.com/jmarantz/838cb6de7e74c0970ea6b63eded0139a | ||
| // contains a patch that will implement batching together to clear multiple | ||
| // histograms. | ||
| // Capture all the pending histograms in a local, clearing the list held in | ||
| // this. Once this occurs, if a new histogram is deleted, a new post will be | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is a bit hard to read because talking about
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will reword |
||
| // required. | ||
| auto histograms = std::make_shared<std::vector<uint64_t>>(); | ||
| { | ||
| Thread::LockGuard lock(hist_mutex_); | ||
| histograms->swap(histograms_to_cleanup_); | ||
| } | ||
|
|
||
| tls_cache_->runOnAllThreads( | ||
| [histogram_id](OptRef<TlsCache> tls_cache) { tls_cache->eraseHistogram(histogram_id); }); | ||
| [histograms](OptRef<TlsCache> tls_cache) { tls_cache->eraseHistograms(*histograms); }); | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.