diff --git a/Control/AthenaConfiguration/python/AccumulatorCache.py b/Control/AthenaConfiguration/python/AccumulatorCache.py index 57684309125440960b2377da4221eb34d497b3bd..9901440f3aabe863a35018b08473765843e9b9e9 100644 --- a/Control/AthenaConfiguration/python/AccumulatorCache.py +++ b/Control/AthenaConfiguration/python/AccumulatorCache.py @@ -1,5 +1,5 @@ # -# Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration +# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration # from AthenaCommon.Logging import logging @@ -75,8 +75,8 @@ class AccumulatorDecorator: def getInfo(self): """Return a dictionary with information about the cache size and cache usage""" return {"cache_size" : len(self._cache), - "misses" : self._stats[self._func].misses, - "hits" : self._stats[self._func].hits, + "misses" : self._stats[self].misses, + "hits" : self._stats[self].hits, "function" : self._func, "result_cache_size" : len(self._resultCache)} @@ -106,6 +106,16 @@ class AccumulatorDecorator: """Resume memoization for all instances of AccumulatorDecorator.""" cls._memoize = True + @classmethod + def clearCache(cls): + """Clear all accumulator caches""" + for decor in cls._stats: + decor._evictAll() + decor._cache.clear() + decor._resultCache.clear() + + cls._stats.clear() + def _getHash(x): if hasattr(x, "athHash"): return x.athHash() @@ -116,8 +126,16 @@ class AccumulatorDecorator: raise NotHashable(x) def _evict(x): + """Called when x is removed from the cache""" if isinstance(x, AccumulatorCachable): x._cacheEvict() + elif isinstance(x, Iterable) and not isinstance(x, str): + for el in x: + AccumulatorDecorator._evict(el) + + def _evictAll(self): + for v in self._cache.values(): + AccumulatorDecorator._evict(v) def __get__(self, obj, objtype): """Support instance methods.""" @@ -137,11 +155,11 @@ class AccumulatorDecorator: finally: t1 = time.perf_counter() if cacheHit is True: - self._stats[self._func].hits += 1 - self._stats[self._func].t_hits += (t1-t0) + self._stats[self].hits += 1 + self._stats[self].t_hits += (t1-t0) elif cacheHit is False: - self._stats[self._func].misses += 1 - self._stats[self._func].t_misses += (t1-t0) + self._stats[self].misses += 1 + self._stats[self].t_misses += (t1-t0) def _callImpl(self, *args, **kwargs): """Implementation of __call__. @@ -204,12 +222,7 @@ class AccumulatorDecorator: return (deepcopy(res) if self._deepcopy else res, False) def __del__(self): - for v in self._cache.values(): - if isinstance(v, Iterable): - for el in v: - AccumulatorDecorator._evict(el) - else: - AccumulatorDecorator._evict(v) + self._evictAll() def AccumulatorCache(func = None, maxSize = 128, diff --git a/Control/AthenaConfiguration/python/ComponentAccumulator.py b/Control/AthenaConfiguration/python/ComponentAccumulator.py index b80b1329b0f5d66b546ab2208f51fd03dbdbf2db..6ad48b3a8194da8dd81601712798e7f469098585 100644 --- a/Control/AthenaConfiguration/python/ComponentAccumulator.py +++ b/Control/AthenaConfiguration/python/ComponentAccumulator.py @@ -163,8 +163,7 @@ class ComponentAccumulator(AccumulatorCachable): return summary def _cleanup(self): - #Delete internal data structures, to be called after all properties are transferred to the C++ application - #Purpose: Free memory + # Delete internal data structures, to be called after all properties are transferred to the C++ application del self._sequence del self._allSequences del self._algorithms @@ -172,6 +171,12 @@ class ComponentAccumulator(AccumulatorCachable): del self._services del self._publicTools del self._auditors + + # Clear all AccumulatorCaches + from AthenaConfiguration.AccumulatorCache import AccumulatorDecorator + AccumulatorDecorator.clearCache() + + # Run garbage collector import gc gc.collect() diff --git a/Control/AthenaConfiguration/test/testAccumulatorCache.py b/Control/AthenaConfiguration/test/testAccumulatorCache.py index 07b8fc17b76533b9a9833866702ebe5e6101a619..4df2927409253c94f9956efbb25726f257abdc15 100755 --- a/Control/AthenaConfiguration/test/testAccumulatorCache.py +++ b/Control/AthenaConfiguration/test/testAccumulatorCache.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -# Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration +# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration # import unittest @@ -52,6 +52,25 @@ class TestCache(unittest.TestCase): info = fac.getInfo() self.assertEqual(info["hits"] , 1) + def test_clear(self): + """Test clearing of all caches""" + @AccumulatorCache + def bar(): + return 42 + + self.assertEqual(bar(), 42) + self.assertEqual(bar(), 42) + self.assertEqual(bar.getInfo()["hits"], 1) + + AccumulatorDecorator.clearCache() + self.assertEqual(bar.getInfo()["cache_size"], 0) + self.assertEqual(bar.getInfo()["hits"], 0) + self.assertEqual(bar.getInfo()["misses"], 0) + self.assertEqual(bar(), 42) + self.assertEqual(bar.getInfo()["misses"], 1) + self.assertEqual(bar(), 42) + self.assertEqual(bar.getInfo()["hits"], 1) + def test_instance_method(self): class Foo: @AccumulatorCache