Account memory usage with custom allocator
Asked Answered
C

4

6

I'm using a custom allocator to account for memory usage in several containers. Currently I use a static variable to account for the memory usage. How could I separate this account across several containers without having to rewrite the allocator to use different static variables?


static size_t allocated = 0;


   template <class T>
   class accounting_allocator {
     public:
       // type definitions
       typedef T        value_type;
       typedef T*       pointer;
       typedef const T* const_pointer;
       typedef T&       reference;
       typedef const T& const_reference;
       typedef std::size_t    size_type;
       typedef std::ptrdiff_t difference_type;
       //static size_t allocated;

       // rebind allocator to type U
       template <class U>
       struct rebind {
           typedef accounting_allocator<U> other;
       };

       // return address of values
       pointer address (reference value) const {
           return &value;
       }
       const_pointer address (const_reference value) const {
           return &value;
       }

       /* constructors and destructor
        * - nothing to do because the allocator has no state
        */
       accounting_allocator() throw() {
       }
       accounting_allocator(const accounting_allocator&) throw() {
       }
       template <class U>
         accounting_allocator (const accounting_allocator<U>&) throw() {
       }
       ~accounting_allocator() throw() {
       }

       // return maximum number of elements that can be allocated
       size_type max_size () const throw() {
        //  std::cout << "max_size()" << std::endl;
           return std::numeric_limits<std::size_t>::max() / sizeof(T);
       }

       // allocate but don't initialize num elements of type T
       pointer allocate (size_type num, const void* = 0) {
           // print message and allocate memory with global new
           //std::cerr << "allocate " << num << " element(s)" << " of size " << sizeof(T) << std::endl;
           pointer ret = (pointer)(::operator new(num*sizeof(T)));
           //std::cerr << " allocated at: " << (void*)ret << std::endl;
           allocated += num * sizeof(T);
            //std::cerr << "allocated: " << allocated/(1024*1024) << " MB" << endl;
           return ret;
       }

       // initialize elements of allocated storage p with value value
       void construct (pointer p, const T& value) {
           // initialize memory with placement new
           new((void*)p)T(value);
      }

       // destroy elements of initialized storage p
       void destroy (pointer p) {
           // destroy objects by calling their destructor
           p->~T();
       }

       // deallocate storage p of deleted elements
       void deallocate (pointer p, size_type num) {
           // print message and deallocate memory with global delete
#if 0
           std::cerr << "deallocate " << num << " element(s)"
                     << " of size " << sizeof(T)
                     << " at: " << (void*)p << std::endl;
#endif
           ::operator delete((void*)p);
           allocated -= num * sizeof(T);
       }
   };
  template<>
    class accounting_allocator<void>
    {
    public:
      typedef size_t      size_type;
      typedef ptrdiff_t   difference_type;
      typedef void*       pointer;
      typedef const void* const_pointer;
      typedef void        value_type;

      template<typename _Tp1>
        struct rebind
        { typedef allocator<_Tp1> other; };
    };


   // return that all specializations of this allocator are interchangeable
   template <class T1, class T2>
   bool operator== (const accounting_allocator<T1>&,
                    const accounting_allocator<T2>&) throw() {
       return true;
   }
   template <class T1, class T2>
   bool operator!= (const accounting_allocator<T1>&,
                    const accounting_allocator<T2>&) throw() {
       return false;
   }
Collarbone answered 27/10, 2009 at 9:22 Comment(2)
Do you mean you want a separate counter for each container type? If so, you could simply include the container type as a template parameter. This way, a separate counter variable will be generated for each type of container.Safir
I meant to have an account for each container that uses this allocator. I thought about building several classes with a static allocated count member, but I'm not sure if I can pass this class as a template parameter to the allocator, wihtout failing when it tries to rebind etc. which uses only one template parameter.Collarbone
S
6

If you mean that you want a separate counter for each container type, you could simply include the container type as a template parameter and uncomment static size_t allocated so it's a static member variable. This way, a separate counter variable will be generated for each type of container.

If you're saying you want a separate counter for each instance of a container, you need to make size_t allocated a non-static member variable. The problem is, you'll also need some kind of hook so you can access the allocation counter from outside each container. The STL allocator design makes it difficult to do this. Some STL containers have a constructor that lets you pass an instance of an allocator, but not all containers support this. On containers that support this, you can include a reference to some global map inside your allocator class, and then pass an instance of your allocator to the constructor of each container. Then, when you call accounting_allocator::allocate(), the allocator would record the number of bytes it has allocated in the global map. Still, I can't see how you could easily associate this information with a particular container instance, since the allocator object does not know which container it belongs to.

Honestly, if you're just collecting debug info, it's probably easier to just define a non static size_t allocated, and have accounting_allocator::allocate() simply output the stats to a file or to stdout. Alternatively, look into using a memory profiler tool for the platform you develop on.

Safir answered 27/10, 2009 at 11:7 Comment(0)
T
0

Put the declaration of "static size_t allocated" into the class defininition. Every template instantation will have a separate counter shared among all objects of this template.

Tollbooth answered 27/10, 2009 at 9:51 Comment(0)
G
0

See my code samples:

// uintptr_t represents an object address
// as a numerical value.
// you could use unsigned long insead if 
// sizeof(long) == sizeof(void*) on your system.
struct AllocCounter {
    static size_t *Register(uintptr_t uContainer)
    {
        // insert container address into map, and
        // return an associated allocation counter.
    }
    static bool Unregister(uintptr_t uContainer)
    {
        // remove container address and the 
        // associated allocation counter from the map
    }
    static void DebugCounter(void)
    {
        // statistic of all container objects.
    }
protected:
    static hash_map<uintptr_t, size_t> m_aCounter;
};

Furthermore, you could associate container or object class name etc with the allocation counter by enhancing above AllocCounter.

And a container example:

class Container 
{
public:
    Container(void) 
    {
        m_pAllocCounter = AllocCounter::Register((uintptr_t)this);
        ....
    }
    ~Container()
    {
        AllocCounter::Unregister((uintptr_t)this);
    }
    pointer ObjectAllocate(void)
    {
        pointer obj;
        *m_pAllocCounter += sizeof *obj;
        obj = new CObject;
        return obj;
    }
    void ObjectDealloc(pointer pObj)
    {
        *m_pAllocCounter -= sizeof *pObj;
        delete pObj;
    }
    ....

private:
    size_t *m_pAllocCounter;
    ....
};
Garrard answered 27/10, 2009 at 12:2 Comment(0)
A
0

This is an old question, but anyway, here's a solution for your issue. The idea is to capture statistics based on the type of the allocator.

So we create a registry that's mapping the allocator's type to a statistic collecting structure like this:

struct registry
{
    struct alloc_track
    {
        size_t count;
        size_t max;
        size_t current;

        alloc_track() : count(0), max(0), current(0) {}
    };

    // Not using a STL container here for the key to avoid allocation */
    std::unordered_map<const char *, alloc_track> registry;

    static registry & get_instance() { static registry a; return a; }

    void credit(const char * key, std::size_t count, std::size_t size) {
        alloc_track & track = registry[key];
        track.count += count;
        track.max += size;
        track.current += size;
    }
    void debit(const char * key, std::size_t count, std::size_t size) {
        alloc_track & track = registry[key];
        track.count -= count;
        track.current -= size;
    }
    void dump() {
        // Not using C++ iostream here to avoid triggering the allocator itself
        printf("Allocator registry:\n");
        for (auto it : registry) {
            printf("%s: %lu instances %lu bytes, max usage %lu\n", it.first, it.second.count, it.second.current, it.second.max);
        }
    }

    ~registry() {
        dump();
    }
};

Then you'll plug the registry in your allocator this way:

template <class T>
class accounting_allocator {
[...]
    // allocate but don't initialize num elements of type T
    pointer allocate (size_type num, const void* = 0) {
        pointer ret = (pointer)(::operator new(num*sizeof(T)));
        registry::get_instance().credit(typeid(T).name(), num, num * sizeof(T));
        return ret;
    }


    // deallocate storage p of deleted elements
    void deallocate (pointer p, size_type num) {
        // print message and deallocate memory with global delete
        ::operator delete((void*)p);
        registry::get_instance().debit(typeid(T).name(), num, num * sizeof(T));
    }

Any time you want to get the current allocation statistics, you can call:

registry::get_instance().dump();

Complete code can be found here

Acidosis answered 21/3, 2022 at 18:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.