Skip to content

Commit ce44c40

Browse files
committed
Add script for testing early recomputation
1 parent 6cf31dc commit ce44c40

3 files changed

Lines changed: 145 additions & 1 deletion

File tree

diskcache/core.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,9 @@ def set(self, key, value, expire=None, read=False, tag=None, retry=False):
707707
:raises Timeout: if database timeout occurs
708708
709709
"""
710+
if expire is not None and expire <= 0:
711+
return False
712+
710713
now = time.time()
711714
db_key, raw = self._disk.put(key)
712715
expire_time = None if expire is None else now + expire

diskcache/memo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def wrapper(*args, **kwargs):
149149
now = time_func()
150150
ttl = expire_time - now
151151

152-
if (-delta * log(random())) < ttl:
152+
if (-delta * early_recompute * log(random())) < ttl:
153153
return result
154154

155155
start = time_func()

tests/test_early_recompute.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"""Early Recomputation Measurements
2+
3+
TODO
4+
5+
* Publish graphs:
6+
1. Cache stampede (single memo decorator).
7+
2. Double-checked locking (memo, barrier, memo).
8+
3. Early recomputation (memo with early recomputation).
9+
4. Advanced usage: adjust "Beta" parameter.
10+
11+
"""
12+
13+
import concurrent.futures
14+
import diskcache as dc
15+
import functools
16+
import matplotlib.pyplot as plt
17+
import shutil
18+
import threading
19+
import time
20+
21+
22+
def make_timer(times):
23+
"""Make a decorator which accumulates (start, end) in `times` for function
24+
calls.
25+
26+
"""
27+
lock = threading.Lock()
28+
def timer(func):
29+
@functools.wraps(func)
30+
def wrapper(*args, **kwargs):
31+
start = time.time()
32+
result = func(*args, **kwargs)
33+
pair = start, time.time()
34+
with lock:
35+
times.append(pair)
36+
return wrapper
37+
return timer
38+
39+
40+
def make_worker(times, delay=1):
41+
"""Make a worker which accumulates (start, end) in `times` and sleeps for
42+
`delay` seconds.
43+
44+
"""
45+
@make_timer(times)
46+
def worker():
47+
time.sleep(delay)
48+
return worker
49+
50+
51+
def make_repeater(func, total=60, delay=0.01):
52+
"""Make a repeater which calls `func` and sleeps for `delay` seconds
53+
repeatedly until `total` seconds have elapsed.
54+
55+
"""
56+
def repeat(num):
57+
start = time.time()
58+
while time.time() - start < total:
59+
func()
60+
time.sleep(delay)
61+
return repeat
62+
63+
64+
def frange(start, stop, step=1e-3):
65+
"Generator for floating point values from `start` to `stop` by `step`."
66+
while start < stop:
67+
yield start
68+
start += step
69+
70+
71+
def plot(cache_times, worker_times):
72+
"Plot concurrent workers and latency."
73+
fig, (workers, latency) = plt.subplots(2, sharex=True)
74+
75+
changes = [(start, 1) for start, _ in worker_times]
76+
changes.extend((stop, -1) for _, stop in worker_times)
77+
changes.sort()
78+
start = (changes[0][0] - 1e-6, 0)
79+
counts = [start]
80+
81+
for mark, diff in changes:
82+
# Re-sample between previous and current data point for a nicer-looking
83+
# line plot.
84+
85+
for step in frange(counts[-1][0], mark):
86+
pair = (step, counts[-1][1])
87+
counts.append(pair)
88+
89+
pair = (mark, counts[-1][1] + diff)
90+
counts.append(pair)
91+
92+
max_x = max(start for start, _ in cache_times)
93+
for step in frange(counts[-1][0], max_x):
94+
pair = (step, counts[-1][1])
95+
counts.append(pair)
96+
97+
x_counts = [x for x, y in counts]
98+
y_counts = [y for x, y in counts]
99+
100+
workers.set_title('Concurrent Workers')
101+
workers.set_ylabel('Workers')
102+
workers.plot(x_counts, y_counts)
103+
104+
latency.set_title('Latency')
105+
latency.set_ylabel('Seconds')
106+
latency.set_xlabel('Time')
107+
x_latency = [start for start, _ in cache_times]
108+
y_latency = [stop - start for start, stop in cache_times]
109+
latency.scatter(x_latency, y_latency)
110+
111+
plt.show()
112+
113+
114+
if __name__ == '__main__':
115+
import argparse
116+
parser = argparse.ArgumentParser()
117+
118+
shutil.rmtree('/tmp/cache', ignore_errors=True)
119+
cache = dc.Cache('/tmp/cache')
120+
121+
count = 16
122+
123+
cache_times = []
124+
worker_times = []
125+
126+
worker = make_worker(worker_times)
127+
decorators = [
128+
make_timer(cache_times),
129+
cache.memoize(expire=10, early_recompute=1.5),
130+
# dc.barrier(cache, dc.Lock),
131+
# cache.memoize(expire=10),
132+
]
133+
for decorator in reversed(decorators):
134+
worker = decorator(worker)
135+
136+
repeater = make_repeater(worker)
137+
138+
with concurrent.futures.ThreadPoolExecutor(count) as executor:
139+
executor.map(repeater, [worker] * count)
140+
141+
plot(cache_times, worker_times)

0 commit comments

Comments
 (0)