Industry - Program analysis and verification - Type-preserving Heap Profiler for C++


Published on

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"

Published in: Technology
  • Be the first to comment

  • Be the first to like this

Industry - Program analysis and verification - Type-preserving Heap Profiler for C++

  1. 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 UnionCo-financed by the European Social Fundgrant agreement no. TÁMOP 4.2.1./B-09/1/KMR-2010-0003
  2. 2. Outline• Motivation• Implementation• Case study• Summary
  3. 3. Outline• Motivation• Implementation• Case study• Summary
  4. 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. 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#
  6. 6. Outline• Motivation• Implementation• Case study• Summary
  7. 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. 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. 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. 10. Implementation (4)• how does it work, what happens underneath?#define new NewTrick() * newtemplate<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. 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. 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 newnew (Mem)#pragma pop_macro("new") std::auto_ptr<int>(new int(5));
  13. 13. Outline• Motivation• Implementation• Case study• Summary
  14. 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. 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. 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
  17. 17. Case study (4)• Before fix• After fix
  18. 18. Outline• Motivation• Implementation• Case study• Summary
  19. 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. 20. Q&A Thank you for your attention! Any questions?Feel free to contact me at