Caching is a process of making frequently used and not often changed data more available and easy to access by placing or copying them at fast accessing and computationally less costly memory space.
The basic working concept of caching is straightforward. If any data is cached, it first looks for data from the faster access memory(where the data is cached). In this case, the application server does not make costly network requests to its database if it is found there. If the cache hit misses, the application server makes a direct request to the database server.
- Faster access to data.
- Reduce computationally costly database server network requests.
- Speed up a slow-performing system.
Let’s Understand Caching With A Scenario:
Cache Eviction Policies:
Random Replacement(RR):
from cachetools import cached, RRCache
import time
@cached(cache = RRCache(maxsize = 3))
def myfunc(n):
s = time.time()
time.sleep(n)
return (f"Executed: {n}")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(5))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(5))
print("Time Taken to execute: ", time.time() - s, "\n")
'''
Result:
Executed: 3
Time Taken to execute: 3.003613233566284
Executed: 3
Time Taken to execute: 0.00010895729064941406
Executed: 2
Time Taken to execute: 2.0036702156066895
Executed: 6
Time Taken to execute: 6.007461309432983
Executed: 5
Time Taken to execute: 5.005535364151001
Executed: 1
Time Taken to execute: 1.0015869140625
Executed: 2
Time Taken to execute: 2.0025484561920166
Executed: 6
Time Taken to execute: 6.006529808044434
Executed: 2
Time Taken to execute: 2.002703905105591
Executed: 1
Time Taken to execute: 0.00014352798461914062
Executed: 5
Time Taken to execute: 5.005505800247192
'''
Least Frequently Used(LFU):
import time
from cachetools import cached, LFUCache
@cached(cache = LFUCache(maxsize = 3))
def myfunc(n):
time.sleep(n)
return (f"Executed: {n}")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(4))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(5))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(5))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(4))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(5))
print("Time Taken to execute: ", time.time() - s, "\n")
'''
Result:
Executed: 3
Time Taken to execute: 3.003491163253784
Executed: 3
Time Taken to execute: 0.00011420249938964844
Executed: 1
Time Taken to execute: 1.0017178058624268
Executed: 1
Time Taken to execute: 0.0001633167266845703
Executed: 2
Time Taken to execute: 2.0025696754455566
Executed: 4
Time Taken to execute: 4.004656791687012
Executed: 5
Time Taken to execute: 5.005551099777222
Executed: 5
Time Taken to execute: 0.00011658668518066406
Executed: 6
Time Taken to execute: 6.006593942642212
Executed: 1
Time Taken to execute: 0.0003552436828613281
Executed: 4
Time Taken to execute: 4.004685401916504
Executed: 5
Time Taken to execute: 0.0001475811004638672
'''
Least Recently Used(LRU):
import time
from cachetools import cached, LRUCache
@cached(cache = LRUCache(maxsize = 3))
def myfunc(n):
# This delay resembles some task
time.sleep(n)
return (f"Executed: {n}")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(4))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
'''
Result:
Executed: 3
Time Taken to execute: 3.003572702407837
Executed: 3
Time Taken to execute: 0.00012040138244628906
Executed: 2
Time Taken to execute: 2.002492666244507
Executed: 1
Time Taken to execute: 1.0015416145324707
Executed: 4
Time Taken to execute: 4.004684925079346
Executed: 1
Time Taken to execute: 0.0001659393310546875
Executed: 3
Time Taken to execute: 3.0035295486450195
'''
First In First Out(FIFO):
import time
CACHE_LIMIT = 3
fifo_cache = []
def myfunc(n):
if n not in fifo_cache:
time.sleep(n)
fifo_cache.append(n)
if len(fifo_cache)>CACHE_LIMIT:
fifo_cache.pop(0)
return (f"Executed: {n}")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(3))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(4))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(5))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
'''
Result:
Executed: 3
Time Taken to execute: 3.003526449203491
Executed: 2
Time Taken to execute: 2.002443552017212
Executed: 3
Time Taken to execute: 0.00011157989501953125
Executed: 1
Time Taken to execute: 1.0014557838439941
Executed: 4
Time Taken to execute: 4.004620790481567
Executed: 5
Time Taken to execute: 5.005484104156494
Executed: 2
Time Taken to execute: 2.0024352073669434
'''
Evicting Cache Entries Based on Both Time and Space:
import time
from functools import lru_cache, wraps
from datetime import datetime, timedelta
def lru_cache_with_time_and_space_parameter(seconds: int, maxsize: int):
def wrapper_cache(func):
func = lru_cache(maxsize=maxsize)(func)
func.lifetime = timedelta(seconds=seconds)
func.expiration = datetime.utcnow() + func.lifetime
@wraps(func)
def wrapped_func(*args, **kwargs):
if datetime.utcnow() >= func.expiration:
func.cache_clear()
func.expiration = datetime.utcnow() + func.lifetime
return func(*args, **kwargs)
return wrapped_func
return wrapper_cache
@lru_cache_with_time_and_space_parameter(5, 32)
def myfunc(n):
# This delay resembles some task
time.sleep(n)
return (f"Executed: {n}")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
'''
Result:
Executed: 2
Time Taken to execute: 2.0024309158325195
Executed: 2
Time Taken to execute: 0.00011897087097167969
Executed: 6
Time Taken to execute: 6.006444454193115
Executed: 6
Time Taken to execute: 6.006414175033569
Executed: 2
Time Taken to execute: 2.0024681091308594
Executed: 1
Time Taken to execute: 1.0027210712432861
Executed: 1
Time Taken to execute: 0.00016355514526367188
'''
import time
from cachetools import cached, TTLCache
@cached(TTLCache(maxsize=12, ttl=5))
def myfunc(n):
# This delay resembles some task
time.sleep(n)
return (f"Executed: {n}")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
'''
Result:
Executed: 2
Time Taken to execute: 2.003089189529419
Executed: 2
Time Taken to execute: 0.0001232624053955078
Executed: 6
Time Taken to execute: 6.006647348403931
Executed: 6
Time Taken to execute: 0.00012040138244628906
Executed: 2
Time Taken to execute: 2.0025570392608643
Executed: 1
Time Taken to execute: 1.000640869140625
Executed: 1
Time Taken to execute: 0.00013566017150878906
'''
Streamlit Caching:
import streamlit as st
import time
@st.cache(suppress_st_warning=True) # ???? Changed this
def myfunc(n):
# This delay resembles some task
time.sleep(n)
return (f"Executed: {n}")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(6))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(2))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
s = time.time()
print(myfunc(1))
print("Time Taken to execute: ", time.time() - s, "\n")
'''
Result:
Executed: 2
Time Taken to execute: 2.1840202808380127
Executed: 2
Time Taken to execute: 0.0053174495697021484
Executed: 6
Time Taken to execute: 6.005890846252441
Executed: 6
Time Taken to execute: 0.0037260055541992188
Executed: 2
Time Taken to execute: 0.0067899227142333984
Executed: 1
Time Taken to execute: 1.0083811283111572
Executed: 1
Time Taken to execute: 0.0014271736145019531
'''