Skip to content

Commit 35eb1a0

Browse files
shortstopb513copybara-github
authored andcommitted
Collect telemetry for how many spans objects are pulled from to fulfill a batch request.
This data may be useful to come up with and/or prioritize optimizations to `RemoveRange` that involve reducing the number of spans we pull from to fulfill a batch request. PiperOrigin-RevId: 828154762 Change-Id: Ief8bd5622bd274ac2a76fba6764fa762b3b562dc
1 parent 5b5dbc2 commit 35eb1a0

File tree

4 files changed

+122
-2
lines changed

4 files changed

+122
-2
lines changed

tcmalloc/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,7 @@ cc_test(
14301430
"@com_google_absl//absl/numeric:bits",
14311431
"@com_google_absl//absl/random",
14321432
"@com_google_absl//absl/strings",
1433+
"@com_google_absl//absl/strings:str_format",
14331434
"@com_google_absl//absl/synchronization",
14341435
"@com_google_absl//absl/time",
14351436
"@com_google_absl//absl/types:span",

tcmalloc/central_freelist.h

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,14 @@ class CentralFreeList {
135135
size_t NumSpansInList(int n) ABSL_LOCKS_EXCLUDED(lock_);
136136
SpanStats GetSpanStats() const;
137137

138-
// Reports span utilization and lifetime histogram stats.
138+
// Reports span utilization, lifetime histogram stats, and number of spans
139+
// used to fill a batch.
139140
void PrintSpanUtilStats(Printer& out);
140141
void PrintSpanLifetimeStats(Printer& out);
142+
void PrintNumSpansUsed(Printer& out);
141143
void PrintSpanUtilStatsInPbtxt(PbtxtRegion& region);
142144
void PrintSpanLifetimeStatsInPbtxt(PbtxtRegion& region);
145+
void PrintNumSpansUsedInPbtxt(PbtxtRegion& region);
143146

144147
// Get number of spans in the histogram bucket. We record spans in the
145148
// histogram indexed by absl::bit_width(allocated). So, instead of using the
@@ -250,6 +253,9 @@ class CentralFreeList {
250253

251254
StatsCounter completed_spans_[kLifetimeBuckets];
252255

256+
// Tracks the number of spans used to fill a batch in RemoveRange
257+
StatsCounters<kMaxObjectsToMove> span_allocations_tracker_;
258+
253259
int LifetimeBucketNum(absl::Duration duration) {
254260
int64_t duration_ms = absl::ToInt64Milliseconds(duration);
255261
auto it = std::upper_bound(lifetime_bucket_bounds_,
@@ -532,9 +538,11 @@ inline int CentralFreeList<Forwarder>::RemoveRange(absl::Span<void*> batch) {
532538
// Use local copy of variable to ensure that it is not reloaded.
533539
size_t object_size = object_size_;
534540
int result = 0;
541+
size_t num_spans = 0;
535542
absl::base_internal::SpinLockHolder h(lock_);
536543

537544
do {
545+
num_spans++;
538546
Span* span = FirstNonEmptySpan();
539547
if (ABSL_PREDICT_FALSE(!span)) {
540548
result += Populate(batch.subspan(result));
@@ -572,6 +580,10 @@ inline int CentralFreeList<Forwarder>::RemoveRange(absl::Span<void*> batch) {
572580
}
573581
result += here;
574582
} while (result < batch.size());
583+
TC_ASSERT_GT(num_spans, 0);
584+
TC_ASSERT_LE(batch.size(), kMaxObjectsToMove);
585+
TC_ASSERT_LE(num_spans, kMaxObjectsToMove);
586+
span_allocations_tracker_[num_spans - 1].LossyAdd(1);
575587
UpdateObjectCounts(-result);
576588
return result;
577589
}
@@ -713,6 +725,18 @@ inline void CentralFreeList<Forwarder>::PrintSpanLifetimeStats(Printer& out) {
713725
out.printf("\n");
714726
}
715727

728+
template <class Forwarder>
729+
inline void CentralFreeList<Forwarder>::PrintNumSpansUsed(Printer& out) {
730+
out.printf("class %3d [ %8zu bytes ] : ", size_class_, object_size_);
731+
for (size_t i = 1; i <= kMaxObjectsToMove; ++i) {
732+
out.printf("%zu : %8zu", i, span_allocations_tracker_[i - 1].value());
733+
if (i < kMaxObjectsToMove) {
734+
out.printf(",");
735+
}
736+
}
737+
out.printf("\n");
738+
}
739+
716740
template <class Forwarder>
717741
inline void CentralFreeList<Forwarder>::PrintSpanUtilStatsInPbtxt(
718742
PbtxtRegion& region) {
@@ -731,6 +755,18 @@ inline void CentralFreeList<Forwarder>::PrintSpanUtilStatsInPbtxt(
731755
}
732756
}
733757

758+
template <class Forwarder>
759+
inline void CentralFreeList<Forwarder>::PrintNumSpansUsedInPbtxt(
760+
PbtxtRegion& region) {
761+
for (size_t i = 1; i <= kMaxObjectsToMove; ++i) {
762+
PbtxtRegion span_allocations_tracker =
763+
region.CreateSubRegion("span_allocations_tracker");
764+
span_allocations_tracker.PrintI64("num_spans", i);
765+
span_allocations_tracker.PrintI64("value",
766+
span_allocations_tracker_[i - 1].value());
767+
}
768+
}
769+
734770
template <class Forwarder>
735771
inline void CentralFreeList<Forwarder>::PrintSpanLifetimeStatsInPbtxt(
736772
PbtxtRegion& region) {

tcmalloc/central_freelist_test.cc

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "absl/memory/memory.h"
3838
#include "absl/numeric/bits.h"
3939
#include "absl/random/random.h"
40+
#include "absl/strings/str_format.h"
4041
#include "absl/strings/str_join.h"
4142
#include "absl/synchronization/mutex.h"
4243
#include "absl/time/clock.h"
@@ -497,7 +498,7 @@ TEST_P(CentralFreeListTest, SinglePopulate) {
497498
TypeParam e(std::get<0>(GetParam()).size, std::get<0>(GetParam()).pages,
498499
std::get<0>(GetParam()).num_to_move, std::get<1>(GetParam()));
499500
// Try to fetch sufficiently large number of objects at startup.
500-
const int num_objects_to_fetch = 10 * e.objects_per_span();
501+
const int num_objects_to_fetch = kMaxObjectsToMove;
501502
std::vector<void*> objects(num_objects_to_fetch, nullptr);
502503
const size_t got = e.central_freelist().RemoveRange(
503504
absl::MakeSpan(objects.data(), num_objects_to_fetch));
@@ -817,6 +818,76 @@ TEST_P(CentralFreeListTest, SpanLifetime) {
817818
CheckLifetimeStats(e, {.completed = {{100000, 1}}});
818819
}
819820

821+
TEST_P(CentralFreeListTest, SpanAllocationTracker) {
822+
TypeParam e(std::get<0>(GetParam()).size, std::get<0>(GetParam()).pages,
823+
std::get<0>(GetParam()).num_to_move, std::get<1>(GetParam()));
824+
825+
const int objects_per_span = e.objects_per_span();
826+
if (objects_per_span == 1) return;
827+
828+
constexpr int kNumSpans = 5;
829+
// Track objects allocated per span.
830+
absl::FixedArray<std::vector<void*>> objects(kNumSpans);
831+
void* batch[kMaxObjectsToMove];
832+
833+
const size_t to_fetch = objects_per_span;
834+
// Allocate all objects from kNumSpans.
835+
for (int span = 0; span < kNumSpans; ++span) {
836+
size_t fetched = 0;
837+
while (fetched < to_fetch) {
838+
const size_t n = to_fetch - fetched;
839+
int got = e.central_freelist().RemoveRange(
840+
absl::MakeSpan(batch, std::min(n, e.batch_size())));
841+
for (int i = 0; i < got; ++i) {
842+
objects[span].push_back(batch[i]);
843+
}
844+
fetched += got;
845+
}
846+
}
847+
848+
// Perform deallocations so that each span contains only one free object.
849+
for (int span = 0; span < kNumSpans; ++span) {
850+
batch[0] = objects[span][0];
851+
e.central_freelist().InsertRange({batch, 1});
852+
objects[span].erase(objects[span].begin(), objects[span].begin() + 1);
853+
}
854+
855+
// Calculate the expected number of allocations served by 1 span. These all
856+
// come from the case where there are no non-empty spans and a new span has to
857+
// be allocated.
858+
int single_spans = objects_per_span / e.batch_size();
859+
if (objects_per_span % e.batch_size() != 0) {
860+
single_spans++;
861+
}
862+
single_spans *= kNumSpans;
863+
864+
// Check txt stats
865+
int got = e.central_freelist().RemoveRange(absl::MakeSpan(batch, kNumSpans));
866+
EXPECT_EQ(got, kNumSpans);
867+
std::string buffer(1024 * 1024, '\0');
868+
Printer printer(&*buffer.begin(), buffer.size());
869+
e.central_freelist().PrintNumSpansUsed(printer);
870+
buffer.resize(strlen(buffer.c_str()));
871+
EXPECT_THAT(
872+
buffer,
873+
testing::AllOf(
874+
testing::HasSubstr(absl::StrFormat("%v : 1", kNumSpans)),
875+
testing::ContainsRegex(absl::StrFormat("1 : *%v", single_spans))));
876+
877+
// Check pbtxt stats
878+
std::string buffer_pbtxt(1024 * 1024, '\0');
879+
Printer printer_pbtxt(&*buffer_pbtxt.begin(), buffer_pbtxt.size());
880+
PbtxtRegion region(printer_pbtxt,
881+
tcmalloc::tcmalloc_internal::PbtxtRegionType::kTop);
882+
e.central_freelist().PrintNumSpansUsedInPbtxt(region);
883+
buffer_pbtxt.resize(strlen(buffer_pbtxt.c_str()));
884+
EXPECT_THAT(buffer_pbtxt,
885+
testing::AllOf(testing::HasSubstr(absl::StrFormat(
886+
"{ num_spans: %v value: 1}", kNumSpans)),
887+
testing::HasSubstr(absl::StrFormat(
888+
"{ num_spans: 1 value: %v}", single_spans))));
889+
}
890+
820891
TEST_P(CentralFreeListTest, MultipleSpans) {
821892
TypeParam e(std::get<0>(GetParam()).size, std::get<0>(GetParam()).pages,
822893
std::get<0>(GetParam()).num_to_move, std::get<1>(GetParam()));

tcmalloc/global_stats.cc

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,17 @@ void DumpStats(Printer& out, int level) {
484484
tc_globals.central_freelist(size_class).PrintSpanLifetimeStats(out);
485485
}
486486

487+
out.printf("\n");
488+
out.printf("------------------------------------------------\n");
489+
out.printf("Central cache freelist: Number of spans used histogram\n");
490+
out.printf(
491+
"Number of spans used to fill a batch of allocations, from 0 to "
492+
"N\n");
493+
out.printf("------------------------------------------------\n");
494+
for (int size_class = 1; size_class < kNumClasses; ++size_class) {
495+
tc_globals.central_freelist(size_class).PrintNumSpansUsed(out);
496+
}
497+
487498
tc_globals.transfer_cache().Print(tc_globals.per_size_class_counts(), out);
488499
tc_globals.sharded_transfer_cache().Print(
489500
tc_globals.per_size_class_counts(), out);
@@ -694,6 +705,7 @@ void DumpStatsInPbtxt(Printer& out, int level) {
694705
tc_globals.central_freelist(size_class).PrintSpanUtilStatsInPbtxt(entry);
695706
tc_globals.central_freelist(size_class)
696707
.PrintSpanLifetimeStatsInPbtxt(entry);
708+
tc_globals.central_freelist(size_class).PrintNumSpansUsedInPbtxt(entry);
697709
}
698710

699711
tc_globals.transfer_cache().PrintInPbtxt(tc_globals.per_size_class_counts(),

0 commit comments

Comments
 (0)