Paper: Type-preserving Heap Profiler for C++
Authors: József Mihalicza, Zoltán Porkoláb and Ábel Gábor
Session: "Industry Track Session 4: Program analysis and Verification"
Boost Fertility New Invention Ups Success Rates.pdf
Industry - Program analysis and verification - Type-preserving Heap Profiler for C++
1. Type-preserving heap profiler for C++
József Mihalicza Zoltán Porkoláb Ábel Gábor
Eötvös Loránd University NNG
Budapest, Hungary, 2011.
Supported by the European Union
Co-financed by the European Social Fund
grant agreement no. TÁMOP 4.2.1./B-09/1/KMR-2010-0003
4. Motivation
• NNG: navigation software on handheld devices
– How much RAM is needed?
What if I do not ask feature A, B, C, will it fit into 32MB?
• Need for detailed memory usage profile per feature
– We already have call stacks in heap profilers
• Difficult to map to feature
– Source location
• Better, but not granulated enough
– Type
• Class types are in direct connection with features
5. Motivation (2)
• Natural clusterisation
• Quick overview of the big players
• Optimisation clues
– Store my bool members in a bitfield?
– How many instances of your class exist in a typical run?
• Languages with more introspection already have this
– Haskell, Objective C, Java, C#
http://book.realworldhaskell.org/read/profiling-and-optimization.html
7. Implementation (1)
• post build techniques
– sampling profiler: captures call stacks
– instrumentation: small code fragments injected
– types have already been lost by this point
• we only see calls to memory allocation primitives (malloc,
HeapAlloc etc.) along with the size parameter in bytes
• during build
– types are available at compile time
– we should choose this one
– compiler support for code instrumentation
• custom enter and exit functions called at each function call
• build a calling context tree: each node represents a call
stack (route from root)
• maintain actual call stack per thread
8. Implementation (2)
• so far we have call stacks
• monitor (de)allocations
– hook allocation routines
• operator new
• malloc (typeless entirely)
– maintain a list of currently allocated blocks, with metainfo
• hashmap: pointer -> metainfo
• metainfo: size, alloc/free time, alloc/free call stack (captured
in situ), pointer to call tree node
• Add type to metainfo: not available in the hooks
– void* operator new (size_t);
– collect metainfo of deallocated blocks in a fixed size buffer,
dump to file when full
• have full history of heap behavior
9. Implementation (3)
• where is type lost?
• std::pair<int,int>* a = new std::pair<int,int>(3,4)
static type: std::pair<int,int>*
– void* operator new (size_t);
static type: void*
– ctor/dtor: does not necessarily exist
– the only proper hook point: new keyword at call site Macro
• initial approaches:
– New((std::pair<int,int>),(3,4))
– New((std::pair<int,int>)(3,4))
– New(new std::pair<int,int>(3,4))
– new std::pair<int,int>(3,4))
• final solution:
– std::pair<int,int>* a = new std::pair<int,int>(3,4)
10. Implementation (4)
• how does it work, what happens underneath?
#define new NewTrick() * new
template<class T>
struct TYPE_IDENTIFIER { static char mDummy; };
struct NewTrick {
template<class T>
T* operator* (T* Ptr) {
RegisterAllocation(Ptr, &TYPE_IDENTIFIER<T>::mDummy);
return Ptr;
}
};
• address of mDummy can be looked up in map file to get real type name T
• A* a = new A(1,2) -> A* a = new NewTrick() * new A(1,2)
• A* NewTrick::operator*<A>(A*) gets called
11. Implementation (5)
Pitfalls (1)
• A* a = new A[5];
// calls ::operator new(sizeof(size_t) + 5 * sizeof(A))
a
5 A A A A A
result of ::operator new
• Only if A has non-trivial destructor
Cannot be detected with current language elements
platform independently
• Actual allocations are traced in ::operator new
Types are registered afterwards in NewTrick
We assumed the pointers are the same.
• A* a = New_<A>(5);
12. Implementation (6)
Pitfalls (2)
• New form of new[] calls: A* a = New_<A>(5);
Since new[] is still valid, we have to force the new form
• Extra parameter to new we are no longer compatible
with placement new
• we have to disable our macros for that time:
#include "undef_new.h"
new (Mem)
or #include "define_new.h"
std::auto_ptr<int>(new int(5));
#pragma push_macro("new")
#undef new
new (Mem)
#pragma pop_macro("new")
std::auto_ptr<int>(new int(5));
14. Case study (1)
• Notepad++ text editor
– Free, open source
– Based on Scintilla text editor widget
– 154 kLOC
– Unknown code, from scratch
– Goal1: Test integration overhead
– Goal2: comprehend memory behavior
• Use case: loading a big file
• Integration issues
– Uses STL
• Custom allocator
– instrument both Scintilla and Notepad++
• ~1 net work day
15. Case study (2)
• Timeline view of loading • Reallocation structure
a big file becomes recognizable
• The big players • The green part vanishes
– SplitVector<char> – Why?
– char
– SplitVector<int>
– other
16. Case study (3)
• Green: char
– Call stack -> it is the undo history
– Lines are appended one by one
• A corresponding undo entry gets created for each
• Why vanishes
– Call stack -> undo history is cleared after load
– Gotcha: undo history is being updated unnecessarily
– Quick fix: not to do that during load
19. Summary
• Advantages
– Type is available in the profiler
– Low footprint
– Easy integration
– Multiplatform
– Scalable
• Disadvantages
– Intrusive
– Does not mix well with class-specific operator new
overriding
• Pinpointed a performance bug in Notepad++
20. Q&A
Thank you for your attention!
Any questions?
Feel free to contact me at jmihalicza@gmail.com