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:
@@ -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;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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_
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário