/*++ Copyright (c) 2006 Microsoft Corporation Module Name: object_allocator.h Abstract: Yet another object allocator. This allocator is supposed to be efficient when there is a collection of worker threads accessing it. Author: Leonardo de Moura (leonardo) 2010-06-09. Revision History: --*/ #ifndef _OBJECT_ALLOCATOR_H_ #define _OBJECT_ALLOCATOR_H_ #include"util.h" #include"vector.h" #define DEFAULT_NUM_WORKERS 8 #define NUM_OBJECTS_PER_PAGE 1024 template struct do_nothing_reset_proc { public: void operator()(T * obj) {} }; template struct simple_reset_proc { public: void operator()(T * obj) { obj->reset(); } }; /** \brief Allocator for T objects. This allocator is supposed to be efficient even when a collection of working threads are accessing it. Assumptions: - T must have an empty constructor. - The destructors for T objects are only invoked when the object_allocator is deleted. - The destructors are not invoked if CallDestructors == false. - The functor ResetProc is invoked for \c ptr when recycle(ptr) or recycle(worker_id, ptr) are invoked. The default ResetProc does nothing. */ template > class object_allocator : public ResetProc { /** \brief Auxiliary allocator for storing object into chunks of memory. */ class region { ptr_vector m_pages; unsigned m_idx; //!< next position in the current page. void allocate_new_page() { T * new_page = static_cast(memory::allocate(sizeof(T) * NUM_OBJECTS_PER_PAGE)); m_pages.push_back(new_page); m_idx = 0; } void call_destructors_for_page(T * page, unsigned end) { T * page_end = page + end; for (; page < page_end; page++) page->~T(); } void call_destructors() { if (CallDestructors) { SASSERT(!m_pages.empty()); typename ptr_vector::iterator it = m_pages.begin(); typename ptr_vector::iterator end = m_pages.end(); end--; call_destructors_for_page(*end, m_idx); for (; it != end; ++it) call_destructors_for_page(*it, NUM_OBJECTS_PER_PAGE); } } void free_memory() { call_destructors(); typename ptr_vector::iterator it = m_pages.begin(); typename ptr_vector::iterator end = m_pages.end(); for (; it != end; ++it) memory::deallocate(*it); } public: region() { allocate_new_page(); } ~region() { free_memory(); } template T * allocate() { SASSERT(!m_pages.empty()); T * r = m_pages.back() + m_idx; if (construct) new (r) T(); m_idx++; if (m_idx == NUM_OBJECTS_PER_PAGE) allocate_new_page(); return r; } void reset() { free_memory(); m_pages.reset(); allocate_new_page(); } unsigned get_objects_count() { return (m_pages.size() - 1) * NUM_OBJECTS_PER_PAGE + m_idx; } }; #ifdef Z3DEBUG bool m_concurrent; //!< True when the allocator can be accessed concurrently. #endif ptr_vector m_regions; vector > m_free_lists; template T * allocate_core(unsigned idx) { ptr_vector & free_list = m_free_lists[idx]; if (!free_list.empty()) { T * r = free_list.back(); free_list.pop_back(); return r; } return m_regions[idx]->allocate(); } void recycle_core(unsigned idx, T * ptr) { ResetProc::operator()(ptr); m_free_lists[idx].push_back(ptr); } public: object_allocator(ResetProc const & r = ResetProc()):ResetProc(r) { DEBUG_CODE(m_concurrent = false;); reserve(DEFAULT_NUM_WORKERS); } ~object_allocator() { std::for_each(m_regions.begin(), m_regions.end(), delete_proc()); } /** \brief Enable/Disable concurrent access. */ void enable_concurrent(bool flag) { DEBUG_CODE(m_concurrent = flag;); } /** \brief Make sure that \c num_workers can access this object allocator concurrently. This method must only be invoked if the allocator is not in concurrent mode. */ void reserve(unsigned num_workers) { SASSERT(!m_concurrent); unsigned old_capacity = capacity(); if (num_workers > old_capacity) { m_regions.resize(num_workers); m_free_lists.resize(num_workers); for (unsigned i = old_capacity; i < capacity(); i++) { m_regions[i] = alloc(region); } } } /** \brief Return the number of workers supported by this object allocator. */ unsigned capacity() const { return m_regions.size(); } /** \brief Free all memory allocated using this allocator. This method must only be invoked when the allocator is not in concurrent mode. */ void reset() { SASSERT(!m_concurrent); unsigned c = capacity(); for (unsigned i = 0; i < c; i++) { m_regions[i]->reset(); m_free_lists[i].reset(); } } /** \brief Allocate a new object. This method must only be invoked when the object_allocator is not in concurrent mode. */ template T * allocate() { SASSERT(!m_concurrent); return allocate_core(0); } /** \brief Recycle the given object. This method must only be invoked when the object_allocator is not in concurrent mode. \remark It is OK to recycle an object allocated by a worker when the object_allocator was in concurrent mode. */ void recycle(T * ptr) { SASSERT(!m_concurrent); recycle_core(0, ptr); } /** \brief Allocate a new object for the given worker. This method must only be invoked when the object_allocator is in concurrent mode. */ template T * allocate(unsigned worker_id) { SASSERT(m_concurrent); return allocate_core(worker_id); } /** \brief Recycle the given object. This method must only be invoked when the object_allocator is in concurrent mode. \remark It is OK to recycle an object allocated by a different worker, or allocated when the object_allocator was not in concurrent mode. */ void recycle(unsigned worker_id, T * ptr) { SASSERT(m_concurrent); return recycle_core(worker_id, ptr); } /** \brief Wrapper for currying worker_id in allocate and recycle methods. */ class worker_object_allocator { object_allocator & m_owner; unsigned m_worker_id; friend class object_allocator; worker_object_allocator(object_allocator & owner, unsigned id):m_owner(owner), m_worker_id(id) {} public: template T * allocate() { return m_owner.allocate(m_worker_id); } void recycle(T * ptr) { return m_owner.recycle(m_worker_id, ptr); } }; /** \brief Return a wrapper for allocating memory for the given worker. The wrapper remains valid even when the object_allocator is not in concurrent mode. However, the methods allocate/recycle of the wrapper must only be invoked when the object_allocator is in concurrent mode. */ worker_object_allocator get_worker_allocator(unsigned worker_id) { SASSERT(worker_id < capacity()); return worker_object_allocator(*this, worker_id); } unsigned get_objects_count() const { unsigned count = 0; unsigned n_regions = m_regions.size(); for (unsigned i = 0; i < n_regions; i++) { count += m_regions[i]->get_objects_count(); count -= m_free_lists[i].size(); } return count; } }; #endif /* _OBJECT_ALLOCATOR_H_ */