Instrument smart allocator

Dumps information about allocation events in smart
allocator, and aggregates some information such as bytes
allocated per callsite in the current request (or across
requests). This information can be gathered in the heaptrace
command, and the information collected during the trace is
enough to find how much memory an object is responsible for
(i.e. keeping reachable).

It's all conditionally compiled because perflab showed a pretty
significant instruction regression if it was a runtime option.
You'll need to fbconfig with --extra-cxxflags=-DMEMORY_PROFILING
in order to use it.

This isn't done, but I wanted to get some feedback on where I
should take this (general design, what to log, where to dump
stuff, etc)
Esse commit está contido em:
Eric Caruso
2013-07-24 14:05:45 -07:00
commit de Sara Golemon
commit 2a793e36dc
14 arquivos alterados com 271 adições e 6 exclusões
+2
Ver Arquivo
@@ -27,6 +27,7 @@ namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
class ArrayInit;
struct MemoryProfile;
class HphpArray : public ArrayData {
enum SortFlavor { IntegerSort, StringSort, GenericSort };
@@ -507,6 +508,7 @@ public:
computeMaxElms(tableMask) * sizeof(HphpArray::Elm);
}
friend struct MemoryProfile;
};
//=============================================================================
+6
Ver Arquivo
@@ -27,6 +27,7 @@
#include "hphp/runtime/base/smart_allocator.h"
#include "hphp/runtime/base/leak_detectable.h"
#include "hphp/runtime/base/sweepable.h"
#include "hphp/runtime/base/memory_profile.h"
#include "hphp/runtime/base/builtin_functions.h"
#include "hphp/runtime/base/runtime_option.h"
#include "hphp/runtime/server/http_server.h"
@@ -467,8 +468,13 @@ void* SmartAllocatorImpl::alloc(size_t nbytes) {
ptr = MM().slabAlloc(nbytes);
}
TRACE(1, "alloc %zu -> %p\n", nbytes, ptr);
MemoryProfile::logAllocation(ptr, nbytes);
return ptr;
}
void SmartAllocatorImpl::logDealloc(void *ptr) {
MemoryProfile::logDeallocation(ptr);
}
///////////////////////////////////////////////////////////////////////////////
}
+132
Ver Arquivo
@@ -0,0 +1,132 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/base/memory_profile.h"
#include "hphp/runtime/base/hphp_value.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
namespace HPHP {
TRACE_SET_MOD(heap);
IMPLEMENT_THREAD_LOCAL(MemoryProfile, MemoryProfile::s_memory_profile);
// stack trace helper
static ProfileStackTrace getStackTrace() {
ProfileStackTrace trace;
if (g_context.isNull()) return trace;
Transl::VMRegAnchor _;
ActRec *fp = g_vmContext->getFP();
if (!fp) return trace;
PC pc = g_vmContext->getPC();
const Func *f = fp->m_func;
Unit *u = f->unit();
Offset off = pc - u->entry();
for (;;) {
trace.push_back({ f, off });
fp = g_vmContext->getPrevVMState(fp, &off);
if (!fp) break;
f = fp->m_func;
}
return trace;
}
void MemoryProfile::startProfilingImpl() {
TRACE(1, "request started: initializing memory profile\n");
m_livePointers.clear();
m_dump.clear();
}
void MemoryProfile::finishProfilingImpl() {
TRACE(1, "request ended, dumping data\n");
}
void MemoryProfile::logAllocationImpl(void *ptr, size_t size) {
TRACE(2, "logging allocation at %p of %lu bytes\n", ptr, size);
ProfileStackTrace trace = getStackTrace();
Allocation alloc { size, trace };
m_livePointers[ptr] = alloc;
m_dump.addAlloc(size, trace);
}
void MemoryProfile::logDeallocationImpl(void *ptr) {
TRACE(2, "logging deallocation at %p\n", ptr);
const auto &it = m_livePointers.find(ptr);
if (it == m_livePointers.end()) return;
const Allocation &alloc = it->second;
m_dump.removeAlloc(alloc.m_size, alloc.m_trace);
m_livePointers.erase(it);
}
// static
size_t MemoryProfile::getSizeOfPtr(void *ptr) {
if (!memory_profiling) return 0;
const MemoryProfile &mp = *s_memory_profile;
const auto &allocIt = mp.m_livePointers.find(ptr);
return allocIt != mp.m_livePointers.end() ? allocIt->second.m_size : 0;
}
// static
size_t MemoryProfile::getSizeOfTV(TypedValue *tv) {
if (!memory_profiling) return 0;
switch (tv->m_type) {
case KindOfString:
return getSizeOfPtr(tv->m_data.pstr);
case KindOfArray:
return getSizeOfArray(tv->m_data.parr);
case KindOfObject:
return getSizeOfObject(tv->m_data.pobj);
case KindOfRef:
return getSizeOfPtr(tv->m_data.pref);
default:
return 0;
}
}
// static
size_t MemoryProfile::getSizeOfArray(ArrayData *arr) {
size_t size = getSizeOfPtr(arr);
if (arr->isHphpArray()) {
// calculate extra size
HphpArray *ha = static_cast<HphpArray *>(arr);
size_t tableSize = HphpArray::computeTableSize(ha->m_tableMask);
size_t maxElms = HphpArray::computeMaxElms(ha->m_tableMask);
if (maxElms > HphpArray::SmallSize) {
size_t hashSize = tableSize * sizeof(HphpArray::ElmInd);
size_t dataSize = maxElms * sizeof(HphpArray::Elm);
size += (hashSize <= sizeof(ha->m_inline_hash))
? dataSize
: dataSize + hashSize;
}
}
return size;
}
// static
size_t MemoryProfile::getSizeOfObject(ObjectData *obj) {
ArrayData* props = obj->o_properties.get();
return getSizeOfPtr(obj) + (props ? getSizeOfArray(props) : 0);
}
}
+83
Ver Arquivo
@@ -0,0 +1,83 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_MEMORY_PROFILE_H_
#define incl_HPHP_MEMORY_PROFILE_H_
#include "hphp/runtime/base/memory_profile.h"
#include "hphp/runtime/base/profile_dump.h"
#include "hphp/util/thread_local.h"
namespace HPHP {
struct MemoryProfile {
static DECLARE_THREAD_LOCAL(MemoryProfile, s_memory_profile);
// structs
struct Allocation {
// Represents an allocation event. Allocation events are uniquely
// identified by a pointer and a generation, where the generation
// is bumped when a pointer is reallocated to something else.
size_t m_size;
ProfileStackTrace m_trace;
};
// Start profiling
static inline void startProfiling() {
if (!memory_profiling) return;
s_memory_profile->startProfilingImpl();
}
// Dumps profiled data
static inline void finishProfiling() {
if (!memory_profiling) return;
s_memory_profile->finishProfilingImpl();
}
// Log allocation event
static inline void logAllocation(void *ptr, size_t size) {
if (!memory_profiling) return;
s_memory_profile->logAllocationImpl(ptr, size);
}
// Log deallocation event
static inline void logDeallocation(void *ptr) {
if (!memory_profiling) return;
s_memory_profile->logDeallocationImpl(ptr);
}
// Gets the size of the memory block most recently allocated at
// the given pointer.
static size_t getSizeOfPtr(void *ptr);
// Gets the amount of heap memory owned by a TypedValue.
static size_t getSizeOfTV(TypedValue *tv);
private:
// implementations
void startProfilingImpl();
void finishProfilingImpl();
void logAllocationImpl(void *ptr, size_t size);
void logDeallocationImpl(void *ptr);
// some helpers to dive into compound TVs
static size_t getSizeOfArray(ArrayData *arr);
static size_t getSizeOfObject(ObjectData *obj);
// Map of live allocations, keyed on their pointers.
std::map<void *, Allocation> m_livePointers;
// Profile dump of the current thread's request.
ProfileDump m_dump;
};
}
#endif // incl_HPHP_MEMORY_PROFILE_H_
+1
Ver Arquivo
@@ -22,6 +22,7 @@
#include "hphp/runtime/base/variable_serializer.h"
#include "hphp/runtime/base/execution_context.h"
#include "hphp/runtime/base/runtime_error.h"
#include "hphp/runtime/base/memory_profile.h"
#include "hphp/util/lock.h"
#include "hphp/runtime/base/class_info.h"
#include "hphp/runtime/ext/ext_closure.h"
+3
Ver Arquivo
@@ -332,6 +332,7 @@ class ObjectData {
CArrRef getDynProps() const { return o_properties; }
void initProperties(int nProp);
// heap profiling helpers
void getChildren(std::vector<TypedValue*> &out) {
// statically declared properties
Slot nProps = m_cls->numDeclProperties();
@@ -443,6 +444,8 @@ class ObjectData {
static_assert(offsetof(ObjectData, m_count) == FAST_REFCOUNT_OFFSET, "");
}
friend struct MemoryProfile;
//============================================================================
// ObjectData fields
+1
Ver Arquivo
@@ -403,6 +403,7 @@ public:
F(uint32_t, JitTargetCacheSize, 64 << 20) \
F(uint32_t, HHBCArenaChunkSize, 64 << 20) \
F(bool, ProfileBC, false) \
F(bool, ProfileHeapAcrossRequests, false) \
F(bool, ProfileHWEnable, true) \
F(string, ProfileHWEvents, string("")) \
F(bool, JitAlwaysInterpOne, false) \
+3
Ver Arquivo
@@ -168,6 +168,7 @@ public:
TRACE(1, "dealloc %p\n", obj);
assert(memset(obj, kSmartFreeFill, m_itemSize));
m_free.push(obj);
if (memory_profiling) logDealloc(obj);
MemoryManager::TheMemoryManager()->getStats().usage -= m_itemSize;
}
void clear() { m_free.clear(); }
@@ -183,6 +184,8 @@ public:
return *m_typeId;
}
void logDealloc(void *ptr);
// keep these frequently used fields together.
private:
TRACE_SET_MOD(smartalloc);
+13 -4
Ver Arquivo
@@ -16,6 +16,7 @@
#include "hphp/runtime/debugger/cmd/cmd_heaptrace.h"
#include "hphp/runtime/base/memory_profile.h"
#include "hphp/runtime/vm/unit.h"
namespace HPHP { namespace Eval {
@@ -76,12 +77,14 @@ static std::map<std::string, GraphFormat> s_formatMap = {
void CmdHeaptrace::sendImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::sendImpl(thrift);
thrift.write(m_accum.typesMap);
thrift.write(m_accum.sizeMap);
thrift.write(m_accum.adjacencyList);
}
void CmdHeaptrace::recvImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::recvImpl(thrift);
thrift.read(m_accum.typesMap);
thrift.read(m_accum.sizeMap);
thrift.read(m_accum.adjacencyList);
}
@@ -138,10 +141,15 @@ static const char *typeName(int8_t type) {
void CmdHeaptrace::printHeap(DebuggerClient &client) {
for (const auto &pair : m_accum.typesMap) {
client.print(folly::stringPrintf("Found TV at %p with type %s (%u)",
(void *)pair.first,
typeName(pair.second),
pair.second));
size_t size = m_accum.sizeMap[pair.first];
std::string sizeStr = size
? folly::stringPrintf(" which consumes %lu bytes", size)
: std::string();
client.print(
folly::stringPrintf("Found TV at %p with type %s%s",
(void *)pair.first,
typeName(pair.second),
sizeStr.c_str()));
std::vector<int64_t> &adjList = m_accum.adjacencyList[pair.first];
if (!adjList.empty()) {
std::string children = " -> found children: ";
@@ -243,6 +251,7 @@ bool CmdHeaptrace::onServer(DebuggerProxy &proxy) {
roots,
[](TypedValue *node, Accum &accum) {
accum.typesMap[(int64_t)node] = (int8_t)node->m_type;
accum.sizeMap[(int64_t)node] = (int64_t)MemoryProfile::getSizeOfTV(node);
},
[](TypedValue *parent, TypedValue *child, Accum &accum) {
accum.adjacencyList[(int64_t)parent].push_back((int64_t)child);
+1
Ver Arquivo
@@ -54,6 +54,7 @@ private:
struct Accum {
std::map<int64_t, int8_t> typesMap;
std::map<int64_t, int64_t> sizeMap;
std::map<int64_t, std::vector<int64_t>> adjacencyList;
} m_accum;
};
+5
Ver Arquivo
@@ -63,6 +63,7 @@
#include "hphp/runtime/server/source_root_info.h"
#include "hphp/runtime/base/extended_logger.h"
#include "hphp/runtime/base/tracer.h"
#include "hphp/runtime/base/memory_profile.h"
#include "hphp/system/systemlib.h"
#include "hphp/runtime/ext/ext_collections.h"
@@ -7194,6 +7195,8 @@ void VMExecutionContext::requestInit() {
profileRequestStart();
MemoryProfile::startProfiling();
#ifdef DEBUG
Class* cls = Unit::GetNamedEntity(s_stdclass.get())->clsList();
assert(cls);
@@ -7202,6 +7205,8 @@ void VMExecutionContext::requestInit() {
}
void VMExecutionContext::requestExit() {
MemoryProfile::finishProfiling();
destructObjects();
syncGdbState();
tx()->requestExit();
+7 -2
Ver Arquivo
@@ -1147,7 +1147,10 @@ void CodeGenerator::cgCallHelper(Asm& a,
// do the call; may use a trampoline
m_tx64->emitCall(a, call);
if (sync != SyncOptions::kNoSyncPoint) {
if (memory_profiling || sync != SyncOptions::kNoSyncPoint) {
// if we are profiling the heap, we always need to sync because
// regs need to be correct during smart allocations no matter
// what
recordSyncPoint(a, sync);
}
@@ -4321,7 +4324,9 @@ void CodeGenerator::recordSyncPoint(Asm& as,
case SyncOptions::kSyncPoint:
break;
case SyncOptions::kNoSyncPoint:
assert(0);
// we can get here if we are memory profiling, since we override the
// normal sync settings and sync anyway
assert(memory_profiling);
}
Offset pcOff = m_curInst->marker().bcOff - m_curInst->marker().func->base();
+6
Ver Arquivo
@@ -1568,6 +1568,12 @@ TranslatorX64::funcPrologue(Func* func, int nPassed, ActRec* ar) {
// Special __call prologue
a. mov_reg64_reg64(rStashedAR, argNumToRegName[0]);
emitCall(a, TCA(TranslatorX64::shuffleArgsForMagicCall));
if (memory_profiling) {
m_fixupMap.recordFixup(
a.frontier(),
Fixup(skFuncBody.offset() - func->base(), func->numSlotsInFrame())
);
}
// if shuffleArgs returns 0, that means this was not a magic call
// and we should proceed to a prologue specialized for nPassed;
// otherwise, proceed to a prologue specialized for nPassed==numParams (2).
+8
Ver Arquivo
@@ -143,6 +143,14 @@ const bool packed_tv =
#endif
;
const bool memory_profiling =
#ifdef MEMORY_PROFILING
true
#else
false
#endif
;
/**
* Guard bug-for-bug hphpi compatibility code with this predicate.
*/