Commit 557f4aad authored by dskiba's avatar dskiba Committed by Commit bot

[tracing] Optimize AllocationRegister and increase max backtrace depth.

This CL both increases maximum backtrace depth (24 -> 48) and reduces
AllocationRegister memory usage (265 -> 42 MiB on 32-bit platforms) at
the expense of performance (~30% hit). See the bug for more details.

BUG=617772

Review-Url: https://codereview.chromium.org/2089253002
Cr-Commit-Position: refs/heads/master@{#403291}
parent 6347726e
...@@ -31,12 +31,23 @@ bool operator==(const Backtrace& lhs, const Backtrace& rhs) { ...@@ -31,12 +31,23 @@ bool operator==(const Backtrace& lhs, const Backtrace& rhs) {
return std::equal(lhs.frames, lhs.frames + lhs.frame_count, rhs.frames); return std::equal(lhs.frames, lhs.frames + lhs.frame_count, rhs.frames);
} }
bool operator!=(const Backtrace& lhs, const Backtrace& rhs) {
return !(lhs == rhs);
}
AllocationContext::AllocationContext(): type_name(nullptr) {} AllocationContext::AllocationContext(): type_name(nullptr) {}
AllocationContext::AllocationContext(const Backtrace& backtrace,
const char* type_name)
: backtrace(backtrace), type_name(type_name) {}
bool operator==(const AllocationContext& lhs, const AllocationContext& rhs) { bool operator==(const AllocationContext& lhs, const AllocationContext& rhs) {
return (lhs.backtrace == rhs.backtrace) && (lhs.type_name == rhs.type_name); return (lhs.backtrace == rhs.backtrace) && (lhs.type_name == rhs.type_name);
} }
bool operator!=(const AllocationContext& lhs, const AllocationContext& rhs) {
return !(lhs == rhs);
}
} // namespace trace_event } // namespace trace_event
} // namespace base } // namespace base
......
...@@ -71,18 +71,20 @@ struct BASE_EXPORT Backtrace { ...@@ -71,18 +71,20 @@ struct BASE_EXPORT Backtrace {
// If the stack is higher than what can be stored here, the bottom frames // If the stack is higher than what can be stored here, the bottom frames
// (the ones closer to main()) are stored. Depth of 12 is enough for most // (the ones closer to main()) are stored. Depth of 12 is enough for most
// pseudo traces (see above), but not for native traces, where we need more. // pseudo traces (see above), but not for native traces, where we need more.
enum { kMaxFrameCount = 24 }; enum { kMaxFrameCount = 48 };
StackFrame frames[kMaxFrameCount]; StackFrame frames[kMaxFrameCount];
size_t frame_count; size_t frame_count;
}; };
bool BASE_EXPORT operator==(const Backtrace& lhs, const Backtrace& rhs); bool BASE_EXPORT operator==(const Backtrace& lhs, const Backtrace& rhs);
bool BASE_EXPORT operator!=(const Backtrace& lhs, const Backtrace& rhs);
// The |AllocationContext| is context metadata that is kept for every allocation // The |AllocationContext| is context metadata that is kept for every allocation
// when heap profiling is enabled. To simplify memory management for book- // when heap profiling is enabled. To simplify memory management for book-
// keeping, this struct has a fixed size. // keeping, this struct has a fixed size.
struct BASE_EXPORT AllocationContext { struct BASE_EXPORT AllocationContext {
AllocationContext(); AllocationContext();
AllocationContext(const Backtrace& backtrace, const char* type_name);
Backtrace backtrace; Backtrace backtrace;
...@@ -95,6 +97,8 @@ struct BASE_EXPORT AllocationContext { ...@@ -95,6 +97,8 @@ struct BASE_EXPORT AllocationContext {
bool BASE_EXPORT operator==(const AllocationContext& lhs, bool BASE_EXPORT operator==(const AllocationContext& lhs,
const AllocationContext& rhs); const AllocationContext& rhs);
bool BASE_EXPORT operator!=(const AllocationContext& lhs,
const AllocationContext& rhs);
// Struct to store the size and count of the allocations. // Struct to store the size and count of the allocations.
struct AllocationMetrics { struct AllocationMetrics {
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
namespace base { namespace base {
namespace trace_event { namespace trace_event {
namespace internal {
namespace { namespace {
size_t GetGuardSize() { size_t GetGuardSize() {
...@@ -25,8 +26,7 @@ size_t GetGuardSize() { ...@@ -25,8 +26,7 @@ size_t GetGuardSize() {
} }
} }
// static void* AllocateGuardedVirtualMemory(size_t size) {
void* AllocationRegister::AllocateVirtualMemory(size_t size) {
size = bits::Align(size, GetPageSize()); size = bits::Align(size, GetPageSize());
// Add space for a guard page at the end. // Add space for a guard page at the end.
...@@ -48,12 +48,11 @@ void* AllocationRegister::AllocateVirtualMemory(size_t size) { ...@@ -48,12 +48,11 @@ void* AllocationRegister::AllocateVirtualMemory(size_t size) {
return addr; return addr;
} }
// static void FreeGuardedVirtualMemory(void* address, size_t allocated_size) {
void AllocationRegister::FreeVirtualMemory(void* address,
size_t allocated_size) {
size_t size = bits::Align(allocated_size, GetPageSize()) + GetGuardSize(); size_t size = bits::Align(allocated_size, GetPageSize()) + GetGuardSize();
munmap(address, size); munmap(address, size);
} }
} // namespace internal
} // namespace trace_event } // namespace trace_event
} // namespace base } // namespace base
...@@ -16,23 +16,21 @@ namespace trace_event { ...@@ -16,23 +16,21 @@ namespace trace_event {
class AllocationRegisterTest : public testing::Test { class AllocationRegisterTest : public testing::Test {
public: public:
// Use a lower number of cells for unittests to avoid reserving a virtual // Use a lower number of backtrace cells for unittests to avoid reserving
// region which is too big. // a virtual region which is too big.
static const uint32_t kNumCellsForTesting = static const size_t kAllocationBuckets =
AllocationRegister::kNumBuckets + 100; AllocationRegister::kAllocationBuckets + 100;
static const size_t kAllocationCapacity = kAllocationBuckets;
static const size_t kBacktraceCapacity = 10;
// Returns the number of cells that the |AllocationRegister| can store per // Returns the number of cells that the |AllocationRegister| can store per
// system page. // system page.
size_t GetNumCellsPerPage() { size_t GetAllocationCapacityPerPage() {
return GetPageSize() / sizeof(AllocationRegister::Cell); return GetPageSize() / sizeof(AllocationRegister::AllocationMap::Cell);
} }
uint32_t GetHighWaterMark(const AllocationRegister& reg) { size_t GetHighWaterMark(const AllocationRegister& reg) {
return reg.next_unused_cell_; return reg.allocations_.next_unused_cell_;
}
uint32_t GetNumCells(const AllocationRegister& reg) {
return reg.num_cells_;
} }
}; };
...@@ -59,7 +57,7 @@ size_t SumAllSizes(const AllocationRegister& reg) { ...@@ -59,7 +57,7 @@ size_t SumAllSizes(const AllocationRegister& reg) {
} }
TEST_F(AllocationRegisterTest, InsertRemove) { TEST_F(AllocationRegisterTest, InsertRemove) {
AllocationRegister reg(kNumCellsForTesting); AllocationRegister reg(kAllocationCapacity, kBacktraceCapacity);
AllocationContext ctx; AllocationContext ctx;
// Zero-sized allocations should be discarded. // Zero-sized allocations should be discarded.
...@@ -93,7 +91,7 @@ TEST_F(AllocationRegisterTest, InsertRemove) { ...@@ -93,7 +91,7 @@ TEST_F(AllocationRegisterTest, InsertRemove) {
} }
TEST_F(AllocationRegisterTest, DoubleFreeIsAllowed) { TEST_F(AllocationRegisterTest, DoubleFreeIsAllowed) {
AllocationRegister reg(kNumCellsForTesting); AllocationRegister reg(kAllocationCapacity, kBacktraceCapacity);
AllocationContext ctx; AllocationContext ctx;
reg.Insert(reinterpret_cast<void*>(1), 1, ctx); reg.Insert(reinterpret_cast<void*>(1), 1, ctx);
...@@ -106,7 +104,7 @@ TEST_F(AllocationRegisterTest, DoubleFreeIsAllowed) { ...@@ -106,7 +104,7 @@ TEST_F(AllocationRegisterTest, DoubleFreeIsAllowed) {
} }
TEST_F(AllocationRegisterTest, DoubleInsertOverwrites) { TEST_F(AllocationRegisterTest, DoubleInsertOverwrites) {
AllocationRegister reg(kNumCellsForTesting); AllocationRegister reg(kAllocationCapacity, kBacktraceCapacity);
AllocationContext ctx; AllocationContext ctx;
StackFrame frame1 = StackFrame::FromTraceEventName("Foo"); StackFrame frame1 = StackFrame::FromTraceEventName("Foo");
StackFrame frame2 = StackFrame::FromTraceEventName("Bar"); StackFrame frame2 = StackFrame::FromTraceEventName("Bar");
...@@ -140,12 +138,12 @@ TEST_F(AllocationRegisterTest, DoubleInsertOverwrites) { ...@@ -140,12 +138,12 @@ TEST_F(AllocationRegisterTest, DoubleInsertOverwrites) {
// register still behaves correctly. // register still behaves correctly.
TEST_F(AllocationRegisterTest, InsertRemoveCollisions) { TEST_F(AllocationRegisterTest, InsertRemoveCollisions) {
size_t expected_sum = 0; size_t expected_sum = 0;
AllocationRegister reg(kNumCellsForTesting); AllocationRegister reg(kAllocationCapacity, kBacktraceCapacity);
AllocationContext ctx; AllocationContext ctx;
// By inserting 100 more entries than the number of buckets, there will be at // By inserting 100 more entries than the number of buckets, there will be at
// least 100 collisions (100 = kNumCellsForTesting - kNumBuckets). // least 100 collisions (100 = kAllocationCapacity - kAllocationBuckets).
for (uintptr_t i = 1; i <= kNumCellsForTesting; i++) { for (uintptr_t i = 1; i <= kAllocationCapacity; i++) {
size_t size = i % 31; size_t size = i % 31;
expected_sum += size; expected_sum += size;
reg.Insert(reinterpret_cast<void*>(i), size, ctx); reg.Insert(reinterpret_cast<void*>(i), size, ctx);
...@@ -157,7 +155,7 @@ TEST_F(AllocationRegisterTest, InsertRemoveCollisions) { ...@@ -157,7 +155,7 @@ TEST_F(AllocationRegisterTest, InsertRemoveCollisions) {
EXPECT_EQ(expected_sum, SumAllSizes(reg)); EXPECT_EQ(expected_sum, SumAllSizes(reg));
for (uintptr_t i = 1; i <= kNumCellsForTesting; i++) { for (uintptr_t i = 1; i <= kAllocationCapacity; i++) {
size_t size = i % 31; size_t size = i % 31;
expected_sum -= size; expected_sum -= size;
reg.Remove(reinterpret_cast<void*>(i)); reg.Remove(reinterpret_cast<void*>(i));
...@@ -177,7 +175,7 @@ TEST_F(AllocationRegisterTest, InsertRemoveCollisions) { ...@@ -177,7 +175,7 @@ TEST_F(AllocationRegisterTest, InsertRemoveCollisions) {
// free list is utilised properly. // free list is utilised properly.
TEST_F(AllocationRegisterTest, InsertRemoveRandomOrder) { TEST_F(AllocationRegisterTest, InsertRemoveRandomOrder) {
size_t expected_sum = 0; size_t expected_sum = 0;
AllocationRegister reg(kNumCellsForTesting); AllocationRegister reg(kAllocationCapacity, kBacktraceCapacity);
AllocationContext ctx; AllocationContext ctx;
uintptr_t generator = 3; uintptr_t generator = 3;
...@@ -217,74 +215,52 @@ TEST_F(AllocationRegisterTest, InsertRemoveRandomOrder) { ...@@ -217,74 +215,52 @@ TEST_F(AllocationRegisterTest, InsertRemoveRandomOrder) {
TEST_F(AllocationRegisterTest, ChangeContextAfterInsertion) { TEST_F(AllocationRegisterTest, ChangeContextAfterInsertion) {
using Allocation = AllocationRegister::Allocation; using Allocation = AllocationRegister::Allocation;
const char kStdString[] = "std::string"; AllocationRegister reg(kAllocationCapacity, kBacktraceCapacity);
AllocationRegister reg(kNumCellsForTesting);
AllocationContext ctx; AllocationContext ctx;
reg.Insert(reinterpret_cast<void*>(17), 1, ctx); reg.Insert(reinterpret_cast<void*>(17), 1, ctx);
reg.Insert(reinterpret_cast<void*>(19), 2, ctx); reg.Insert(reinterpret_cast<void*>(19), 2, ctx);
reg.Insert(reinterpret_cast<void*>(23), 3, ctx); reg.Insert(reinterpret_cast<void*>(23), 3, ctx);
Allocation a;
// Looking up addresses that were not inserted should return null. // Looking up addresses that were not inserted should return null.
// A null pointer lookup is a valid thing to do. // A null pointer lookup is a valid thing to do.
EXPECT_EQ(nullptr, reg.Get(nullptr)); EXPECT_FALSE(reg.Get(nullptr, &a));
EXPECT_EQ(nullptr, reg.Get(reinterpret_cast<void*>(13))); EXPECT_FALSE(reg.Get(reinterpret_cast<void*>(13), &a));
Allocation* a17 = reg.Get(reinterpret_cast<void*>(17));
Allocation* a19 = reg.Get(reinterpret_cast<void*>(19));
Allocation* a23 = reg.Get(reinterpret_cast<void*>(23));
EXPECT_NE(nullptr, a17); EXPECT_TRUE(reg.Get(reinterpret_cast<void*>(17), &a));
EXPECT_NE(nullptr, a19); EXPECT_TRUE(reg.Get(reinterpret_cast<void*>(19), &a));
EXPECT_NE(nullptr, a23); EXPECT_TRUE(reg.Get(reinterpret_cast<void*>(23), &a));
a17->size = 100;
a19->context.type_name = kStdString;
reg.Remove(reinterpret_cast<void*>(23)); reg.Remove(reinterpret_cast<void*>(23));
// Lookup should not find any garbage after removal. // Lookup should not find any garbage after removal.
EXPECT_EQ(nullptr, reg.Get(reinterpret_cast<void*>(23))); EXPECT_FALSE(reg.Get(reinterpret_cast<void*>(23), &a));
// Mutating allocations should have modified the allocations in the register.
for (const Allocation& allocation : reg) {
if (allocation.address == reinterpret_cast<void*>(17))
EXPECT_EQ(100u, allocation.size);
if (allocation.address == reinterpret_cast<void*>(19))
EXPECT_EQ(kStdString, allocation.context.type_name);
}
reg.Remove(reinterpret_cast<void*>(17)); reg.Remove(reinterpret_cast<void*>(17));
reg.Remove(reinterpret_cast<void*>(19)); reg.Remove(reinterpret_cast<void*>(19));
EXPECT_EQ(nullptr, reg.Get(reinterpret_cast<void*>(17))); EXPECT_FALSE(reg.Get(reinterpret_cast<void*>(17), &a));
EXPECT_EQ(nullptr, reg.Get(reinterpret_cast<void*>(19))); EXPECT_FALSE(reg.Get(reinterpret_cast<void*>(19), &a));
} }
// Check that the process aborts due to hitting the guard page when inserting // Check that the process aborts due to hitting the guard page when inserting
// too many elements. // too many elements.
#if GTEST_HAS_DEATH_TEST #if GTEST_HAS_DEATH_TEST
TEST_F(AllocationRegisterTest, OverflowDeathTest) { TEST_F(AllocationRegisterTest, OverflowDeathTest) {
// Use a smaller register to prevent OOM errors on low-end devices. const size_t allocation_capacity = GetAllocationCapacityPerPage();
AllocationRegister reg(static_cast<uint32_t>(GetNumCellsPerPage())); AllocationRegister reg(allocation_capacity, kBacktraceCapacity);
AllocationContext ctx; AllocationContext ctx;
uintptr_t i; size_t i;
// Fill up all of the memory allocated for the register. |GetNumCells(reg)| // Fill up all of the memory allocated for the register's allocation map.
// minus 1 elements are inserted, because cell 0 is unused, so this should for (i = 0; i < allocation_capacity; i++) {
// fill up the available cells exactly. reg.Insert(reinterpret_cast<void*>(i + 1), 1, ctx);
for (i = 1; i < GetNumCells(reg); i++) {
reg.Insert(reinterpret_cast<void*>(i), 1, ctx);
} }
// Adding just one extra element might still work because the allocated memory // Adding just one extra element should cause overflow.
// is rounded up to the page size. Adding a page full of elements should cause ASSERT_DEATH(reg.Insert(reinterpret_cast<void*>(i + 1), 1, ctx), "");
// overflow.
const size_t cells_per_page = GetNumCellsPerPage();
ASSERT_DEATH(for (size_t j = 0; j < cells_per_page; j++) {
reg.Insert(reinterpret_cast<void*>(i + j), 1, ctx);
}, "");
} }
#endif #endif
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
namespace base { namespace base {
namespace trace_event { namespace trace_event {
namespace internal {
namespace { namespace {
size_t GetGuardSize() { size_t GetGuardSize() {
...@@ -20,8 +21,7 @@ size_t GetGuardSize() { ...@@ -20,8 +21,7 @@ size_t GetGuardSize() {
} }
} }
// static void* AllocateGuardedVirtualMemory(size_t size) {
void* AllocationRegister::AllocateVirtualMemory(size_t size) {
size = bits::Align(size, GetPageSize()); size = bits::Align(size, GetPageSize());
// Add space for a guard page at the end. // Add space for a guard page at the end.
...@@ -50,14 +50,13 @@ void* AllocationRegister::AllocateVirtualMemory(size_t size) { ...@@ -50,14 +50,13 @@ void* AllocationRegister::AllocateVirtualMemory(size_t size) {
return addr; return addr;
} }
// static void FreeGuardedVirtualMemory(void* address, size_t allocated_size) {
void AllocationRegister::FreeVirtualMemory(void* address,
size_t allocated_size) {
// For |VirtualFree|, the size passed with |MEM_RELEASE| mut be 0. Windows // For |VirtualFree|, the size passed with |MEM_RELEASE| mut be 0. Windows
// automatically frees the entire region that was reserved by the // automatically frees the entire region that was reserved by the
// |VirtualAlloc| with flag |MEM_RESERVE|. // |VirtualAlloc| with flag |MEM_RESERVE|.
VirtualFree(address, 0, MEM_RELEASE); VirtualFree(address, 0, MEM_RELEASE);
} }
} // namespace internal
} // namespace trace_event } // namespace trace_event
} // namespace base } // namespace base
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment