A Python script can spend much of its time repeating the same work. In one reported test, adding functools.lru_cache to a date-parsing function cut a million-row program’s single-run time from 3.71 seconds to 1.20 seconds. The result depended on the input: the rows contained only 365 distinct date strings. When the dates were all unique, caching made the same author’s separate test slightly slower.
The useful lesson is not that a cache makes Python three times faster. It is to profile the program, check whether an expensive function receives repeated inputs, then measure a focused change against your actual workload.
As an Amazon Associate I earn from qualifying purchases.
What changed in the 3.71-to-1.20-second test?
DevLog’s synthetic program generated one million sales rows, parsed date strings, aggregated revenue by month and region, and wrote a text report. The reported machine was a Mac mini M4 Pro with 48 GB of memory, running Python 3.14.6. In a single in-script timing, the program took 3.71 seconds before the change and 1.20 seconds after it. DevLog also reported that the resulting text files were byte-for-byte equal. These are the author’s measurements on one machine, not an independently reproduced benchmark. DevLog’s report
Recommended Free Tools
The change was to memoize the date-parsing function: after parsing an argument once, the function can return the saved result when it receives the same argument again. The input contained 365 distinct date strings among one million rows. DevLog’s cache statistics showed 365 misses and 999,635 hits, so nearly every call could reuse a prior parse.
#1 Best Overall
Why did date parsing stand out?
Profiling showed strptime as the leading self-time function, with one million calls and 3.045 seconds of self time in a profiled run lasting 8.440 seconds. The much longer profiled time is important: profiling adds overhead. The 8.440-second profile was useful for locating a hot spot, not for comparing the program’s speed before and after the change. Python documentation: The Python Profilers
Python’s documentation puts the distinction plainly: “The profiler modules are designed to provide an execution profile for a given program, not for benchmarking purposes (for that, there is timeit for reasonably accurate results).” Use a profiler to learn where execution time goes; compare performance with unprofiled timings under consistent conditions.
Rank #2
What the repeated-run results show
The same DevLog report gives a separate comparison based on five runs per version and whole-process timing. The values below are the report’s medians. They use a different timing scope from the single-run 3.71-second and 1.20-second figures, so compare plain and cached timings within each row rather than mixing these results.
| Distinct date strings in the input | Plain median | Cached median | Reported outcome |
|---|---|---|---|
| 365 | 3.96 s | 1.43 s | 2.77× speedup; same output |
| 20,000 | 3.70 s | 1.49 s | 2.48× speedup; same output |
| 1,000,000 | 3.76 s | 3.98 s | 0.95×; caching was about 6% slower; same output |
In this experiment, caching helped when the date strings repeated and hurt slightly when every date was unique. The cache still has lookup and storage costs; without repeated arguments to reuse, those costs can outweigh the avoided parsing. The measurements describe this synthetic workload and setup, not a general performance guarantee. DevLog’s report
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.How to add a cache to a suitable function
For a pure function—one whose result depends on its arguments and that has no side effects—the standard-library decorator can store results by argument. The date-parser example looks like this:
from functools import lru_cache
@lru_cache(maxsize=None)
def parse_date(s):
return datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
maxsize=None allows the cache to grow without a limit. That is reasonable only when the number of distinct arguments is bounded and the memory use is acceptable. Python requires cached arguments to be hashable. Its documentation also cautions against caching functions with side effects, functions expected to create distinct mutable objects, or functions that are not pure. Python documentation: functools.lru_cache
Inspect the cache rather than assuming it is useful. Calling parse_date.cache_info() reports hits, misses, the maximum size, and the current size. A high hit count suggests reuse; a cache that keeps accumulating entries may consume more memory than the optimization is worth.
Free tools Windows power users keep installed
One-click scans. No signup required.
Quick Recap
Best Value
A practical way to find and test a bottleneck
- Profile first. Use
cProfileto identify functions consuming time and inspect both call counts and time per function. Treat the profile as diagnostic data, not a benchmark. - Check input repetition. For a candidate function, determine how many calls it receives and how many distinct argument values occur. In the date example, one million calls involved only 365 distinct strings.
- Confirm caching is appropriate. The function should be safe to reuse for identical arguments, its inputs must be hashable, and the expected number of distinct inputs should fit the memory budget.
- Make one focused change. Add the cache to the hot function rather than changing unrelated parts of the program at the same time.
- Compare unprofiled runs consistently. Use a repeatable benchmark such as
timeitwhere appropriate; keep the workload and timing scope the same for both versions. - Verify behavior and cache use. Compare outputs, then inspect cache hits, misses, and size. Keep the change only if the real workload is faster without unacceptable memory growth or behavior changes.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




