Unbounded lru_cache growth in TheAlgorithms Python memoized DP examples
0 reputation · 16 May 2023, 23:04 UTC
0 reputation · 16 May 2023, 23:04 UTC
TheAlgorithms/Python uses functools.lru_cache (or functools.cache on Python 3.9+) to memoize recursive dynamic-programming implementations such as Fibonacci, knapsack, and coin-change variants. Many of these decorators are applied without a maxsize, so every distinct argument tuple is retained for the lifetime of the process.
My goal is to reuse one of these implementations inside a long-running script that calls the function with many distinct inputs, without memory growing indefinitely and without silently returning outdated results if underlying inputs change between calls. The uncertainty is that the repositories appear to have no consistent convention: some files use unbounded decorators, some use manual dictionaries, and none seem to document an eviction or invalidation policy.
Assume CPython 3.10+ and the current master branch of TheAlgorithms/Python; behavior can be checked per file via .cache_info().
Is unbounded growth an accepted trade-off for these educational implementations, or is bounding the cache with an explicit maxsize considered safe for their correctness? Does the project have any stated guidance on choosing between lru_cache, manual dict memoization, and iterative DP? And is there any intended invalidation story when memoized functions close over mutable inputs?
32275 reputation · 17 May 2023, 06:16 UTC
In the current master branch of TheAlgorithms/Python, many recursive dynamic programming (DP) implementations use @functools.lru_cache or @functools.cache without a specified maxsize. This results in unbounded growth where every unique argument tuple is stored for the process lifetime. For long-running scripts with high input diversity, this will lead to memory exhaustion. Bounding the cache with an explicit maxsize is safe and does not affect the correctness of these algorithms, as DP results depend solely on the input arguments.
The unbounded nature of these caches is likely an accepted trade-off for educational clarity. In a teaching context, the priority is demonstrating the concept of memoization rather than production-grade resource management. Since typical educational inputs are small, the memory footprint remains negligible during a standard demo.
The project does not appear to have a stated global guidance on choosing a specific strategy. The choice generally depends on the following requirements:
RecursionError and the overhead of cache management.There is no built-in invalidation story for functions that close over mutable inputs in these examples. Because lru_cache keys are based on the arguments passed, it cannot detect if an external mutable object (like a list used in a closure) has changed. If the underlying data changes, the cache will return stale results.
@cache or @lru_cache() with @lru_cache(maxsize=1024) (adjust the size based on your memory budget).function_name.cache_info() to monitor currsize and misses to ensure the bound is working and not causing excessive thrashing.tuple(my_list)) as an argument or call function_name.cache_clear() when the state updates.tracemalloc to confirm that memory stabilizes once the maxsize is reached.Are the functions you are reusing closing over any mutable global variables or class attributes that change during the script's execution?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.