diff --git a/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.h b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.h new file mode 100644 index 0000000000000000000000000000000000000000..45f3fd46d626a8cba67144f9e7ce0f48c1b465a2 --- /dev/null +++ b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.h @@ -0,0 +1,141 @@ +// This file's extension implies that it's C, but it's really -*- C++ -*-. +/* + * Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file AthenaKernel/RecyclableDataObject.h + * @author scott snyder <snyder@bnl.gov> + * @date Oct, 2018 + * @brief Helper for recycling objects from event to event. + */ + + +#ifndef ATHENAKERNEL_RECYCLABLEDATAOBJECT_H +#define ATHENAKERNEL_RECYCLABLEDATAOBJECT_H + + +// Work around a warning in tbb, found by gcc8. +// Fixed in TBB 2018 U5. +#if defined(__GNUC__) && __GNUC__ >= 8 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wclass-memaccess" +#endif +#include "tbb/concurrent_queue.h" +#if defined(__GNUC__) && __GNUC__ >= 8 +# pragma GCC diagnostic pop +#endif + + +namespace Athena { + + +/** + * @brief Helper for recycling objects from event to event. + * + * In some cases, one wants to reuse already-allocated objects from event to event, + * rather than deleting and recreating them each time. This has typically been done + * for complicated objects, such as classes deriving from IdentfiableContainer. + * + * Some care is obviously needed for this to work in a MT job. Rather than holding + * a pointer to a specific object from a class member, we instead maintain + * a concurrent_queue of pointers to objects. When we want an object, we get + * one from the queue; if the queue is empty, then we allocate a new one. + * + * The object used should derive from @c DataObject. We bump the reference count + * by 1 when the object is allocated so that it won't get deleted by StoreGate. + * We also override @c release() so that when the reference count goes back + * to 1 it is `recycled'. When this happens, we call the @c recycle() method + * on the object (which should probably be protected) and put the object + * back on the queue to be allocated again. + * + * Example use: + *@code + * class MyDataObj : public DataObject { ... + * public: + * MyDataObj (bool createColl); ... + * protected: + * void recycle(); + * ... }; + * + * class MyConverter { ... + * private: + * RecyclableDataQueue<MyDataObj> m_queue; + * ... }; + * + * StatusCode MyConverter::createObj (...) { ... + * MyDataObj* obj = m_queue.get (true); + @endcode + * + * @c DOBJ should derive from @c DataObject and should provide a member @c recycle(). + */ +template <class DOBJ> +class RecyclableDataObject : public DOBJ +{ +public: + /// Underlying queue type holding these objects. + typedef tbb::concurrent_queue<DOBJ*> queue_t; + + + /** + * @brief Constructor. + * @param queue Queue on which this object will be entered when it is released. + * @param args... Additional arguments to pass to the @c DOBJ constructor. + */ + template <typename... ARGS> + RecyclableDataObject (queue_t& queue, ARGS&&... args); + + + /** + * @brief DataObject release method. + * + * This overrides the @c release() method of @c DataObject. + * Once the reference count falls back to 1 (meaning that the object is no longer + * referenced from StoreGate), we recycle the object back to the queue. + */ + virtual unsigned long release() override; + + +private: + /// The queue on which this object will be placed when it is recycled. + queue_t& m_queue; +}; + + +/** + * @brief Holder for recyclable objects. + * + * See @c RecyclableDataObject. + */ +template <class DOBJ> +class RecyclableDataQueue + : public RecyclableDataObject<DOBJ>::queue_t +{ +public: + /// Underlying queue type holding these objects. + using RecyclableDataObject<DOBJ>::queue_t::queue_t; + + + /** + * @brief Get an object, either a new one or one recycled from a previous event. + * @param args... Arguments to pass to the @c DOBJ constructor if we make a new one. + */ + template <typename... ARGS> + DOBJ* get (ARGS&&... args); + + + /** + * @brief Destructor. + * + * Free all objects in the queue. + */ + ~RecyclableDataQueue(); +}; + + +} // namespace Athena + + +#include "AthenaKernel/RecyclableDataObject.icc" + + +#endif // not ATHENAKERNEL_RECYCLABLEDATAOBJECT_H diff --git a/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.icc b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.icc new file mode 100644 index 0000000000000000000000000000000000000000..870f26dad722e462a340db311e42fdabe9805d98 --- /dev/null +++ b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.icc @@ -0,0 +1,82 @@ +// $Id$ +/* + * Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration. + */ +/** + * @file AthenaKernel/RecyclableDataObject.icc + * @author scott snyder <snyder@bnl.gov> + * @date Oct, 2018 + * @brief Helper for recycling objects from event to event. + */ + + +namespace Athena { + + +/** + * @brief Constructor. + * @param queue Queue on which this object will be entered when it is released. + * @param args... Additional arguments to pass to the @c DOBJ constructor. + */ +template <class DOBJ> +template <typename... ARGS> +RecyclableDataObject<DOBJ>::RecyclableDataObject (queue_t& queue, ARGS&&... args) + : DOBJ (std::forward<ARGS>(args)...), + m_queue (queue) +{ + // Bump reference count by one so that these objects won't be deleted by StoreGate. + this->addRef(); +} + + +/** + * @brief DataObject release method. + * + * This overrides the @c release() method of @c DataObject. + * Once the reference count falls back to 1 (meaning that the object is no longer + * referenced from StoreGate), we recycle the object back to the queue. + */ +template <class DOBJ> +unsigned long RecyclableDataObject<DOBJ>::release() +{ + unsigned long cnt = DOBJ::release(); + if (cnt == 1) { + this->recycle(); + m_queue.push (this); + } + return cnt; +} + + +/** + * @brief Get an object, either a new one or one recycled from a previous event. + * @param args... Arguments to pass to the @c DOBJ constructor if we make a new one. + */ +template <class DOBJ> +template <typename... ARGS> +DOBJ* RecyclableDataQueue<DOBJ>::get (ARGS&&... args) +{ + DOBJ* obj = nullptr; + if (this->try_pop (obj)) { + return obj; + } + return new RecyclableDataObject<DOBJ> (*this, std::forward<ARGS>(args)...); +} + + +/** + * @brief Destructor. + * + * Free all objects in the queue. + */ +template <class DOBJ> +RecyclableDataQueue<DOBJ>::~RecyclableDataQueue() +{ + DOBJ* obj = nullptr; + while (this->try_pop (obj)) { + delete obj; + } +} + + +} // namespace Athena diff --git a/Control/AthenaKernel/CMakeLists.txt b/Control/AthenaKernel/CMakeLists.txt index 0cdda1adc8e927d8b6adadb0a909f099325ae1e5..99a1e35c6db90cba27c40d20f9bb73328d802535 100644 --- a/Control/AthenaKernel/CMakeLists.txt +++ b/Control/AthenaKernel/CMakeLists.txt @@ -21,6 +21,7 @@ find_package( Boost COMPONENTS program_options regex filesystem thread ) find_package( ROOT COMPONENTS Core ) find_package( UUID ) find_package(CLHEP) +find_package( TBB ) # Only link agains the RT library on Linux: set( rt_library ) if( UNIX AND NOT APPLE ) @@ -134,3 +135,8 @@ atlas_add_test( MetaContDataBucket_test atlas_add_test( TopBase_test SOURCES test/TopBase_test.cxx LINK_LIBRARIES AthenaKernel TestTools ) + +atlas_add_test( RecyclableDataObject_test + SOURCES test/RecyclableDataObject_test.cxx + INCLUDE_DIRS ${TBB_INCLUDE_DIRS} + LINK_LIBRARIES AthenaKernel TestTools ${TBB_LIBRARIES} ) diff --git a/Control/AthenaKernel/share/RecyclableDataObject_test.ref b/Control/AthenaKernel/share/RecyclableDataObject_test.ref new file mode 100644 index 0000000000000000000000000000000000000000..7250f763119a9b04b294338d16ad45c33b427443 --- /dev/null +++ b/Control/AthenaKernel/share/RecyclableDataObject_test.ref @@ -0,0 +1,2 @@ +RecyclableDataObject_test +test1 diff --git a/Control/AthenaKernel/test/RecyclableDataObject_test.cxx b/Control/AthenaKernel/test/RecyclableDataObject_test.cxx new file mode 100644 index 0000000000000000000000000000000000000000..dfa48c25972365c3c0f76c5714a604fbaaaaa02a --- /dev/null +++ b/Control/AthenaKernel/test/RecyclableDataObject_test.cxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration. + */ +// $Id$ +/** + * @file AthenaKernel/test/RecyclableDataObject_test.cxx + * @author scott snyder <snyder@bnl.gov> + * @date Oct, 2018 + * @brief Tests for RecyclableDataObject. + */ + + +#undef NDEBUG +#include "AthenaKernel/RecyclableDataObject.h" +#include "GaudiKernel/DataObject.h" +#include <atomic> +#include <iostream> +#include <cassert> + + +class MyDataObj + : public DataObject +{ +public: + MyDataObj (int x) : m_x (x) + { ++s_count; } + ~MyDataObj() { --s_count; } + + void recycle(); + + static std::atomic<int> s_count; + int m_x = 0; + bool m_recycled = false; +}; + + +std::atomic<int> MyDataObj::s_count (0); + + +void MyDataObj::recycle() +{ + m_recycled = true; +} + + +void test1() +{ + std::cout << "test1\n"; + { + Athena::RecyclableDataQueue<MyDataObj> queue; + + MyDataObj* obj1 = queue.get (1); + obj1->addRef(); + assert (MyDataObj::s_count == 1); + assert (obj1->m_x == 1); + assert (!obj1->m_recycled); + + MyDataObj* obj2 = queue.get (2); + obj2->addRef(); + assert (MyDataObj::s_count == 2); + assert (obj2->m_x == 2); + assert (!obj2->m_recycled); + + obj1->release(); + + MyDataObj* obj3 = queue.get (3); + obj3->addRef(); + assert (MyDataObj::s_count == 2); + assert (obj3->m_x == 1); + assert (obj3->m_recycled); + + obj2->release(); + obj1->release(); + } + assert (MyDataObj::s_count == 0); +} + + +int main() +{ + std::cout << "RecyclableDataObject_test\n"; + test1(); + return 0; +}