Skip to content

Commit 9370823

Browse files
committed
Add a benchmark implementation for the Cython API.
1 parent ef6e252 commit 9370823

File tree

1 file changed

+158
-16
lines changed

1 file changed

+158
-16
lines changed

lockbench.py

Lines changed: 158 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
import sys
12

23
from threading import Thread
34
from threading import RLock
45
from fastrlock.rlock import FastRLock as FLock
56

7+
# Benchmark functions:
8+
9+
cython_code = []
610

711
def lock_unlock(l):
812
l.acquire()
@@ -17,6 +21,21 @@ def lock_unlock(l):
1721
l.release()
1822

1923

24+
cython_code.append("""
25+
def lock_unlock(lock):
26+
lock_fastrlock(lock, -1, True)
27+
unlock_fastrlock(lock)
28+
lock_fastrlock(lock, -1, True)
29+
unlock_fastrlock(lock)
30+
lock_fastrlock(lock, -1, True)
31+
unlock_fastrlock(lock)
32+
lock_fastrlock(lock, -1, True)
33+
unlock_fastrlock(lock)
34+
lock_fastrlock(lock, -1, True)
35+
unlock_fastrlock(lock)
36+
""")
37+
38+
2039
def reentrant_lock_unlock(l):
2140
l.acquire()
2241
l.acquire()
@@ -30,6 +49,21 @@ def reentrant_lock_unlock(l):
3049
l.release()
3150

3251

52+
cython_code.append("""
53+
def reentrant_lock_unlock(lock):
54+
lock_fastrlock(lock, -1, True)
55+
lock_fastrlock(lock, -1, True)
56+
lock_fastrlock(lock, -1, True)
57+
lock_fastrlock(lock, -1, True)
58+
lock_fastrlock(lock, -1, True)
59+
unlock_fastrlock(lock)
60+
unlock_fastrlock(lock)
61+
unlock_fastrlock(lock)
62+
unlock_fastrlock(lock)
63+
unlock_fastrlock(lock)
64+
""")
65+
66+
3367
def mixed_lock_unlock(l):
3468
l.acquire()
3569
l.release()
@@ -43,6 +77,21 @@ def mixed_lock_unlock(l):
4377
l.release()
4478

4579

80+
cython_code.append("""
81+
def mixed_lock_unlock(lock):
82+
lock_fastrlock(lock, -1, True)
83+
unlock_fastrlock(lock)
84+
lock_fastrlock(lock, -1, True)
85+
lock_fastrlock(lock, -1, True)
86+
unlock_fastrlock(lock)
87+
lock_fastrlock(lock, -1, True)
88+
unlock_fastrlock(lock)
89+
lock_fastrlock(lock, -1, True)
90+
unlock_fastrlock(lock)
91+
unlock_fastrlock(lock)
92+
""")
93+
94+
4695
def context_manager(l):
4796
with l: pass
4897
with l:
@@ -77,6 +126,24 @@ def lock_unlock_nonblocking(l):
77126
l.release()
78127

79128

129+
cython_code.append("""
130+
def lock_unlock_nonblocking(lock):
131+
if lock_fastrlock(lock, -1, False):
132+
unlock_fastrlock(lock)
133+
if lock_fastrlock(lock, -1, False):
134+
unlock_fastrlock(lock)
135+
if lock_fastrlock(lock, -1, False):
136+
unlock_fastrlock(lock)
137+
if lock_fastrlock(lock, -1, False):
138+
unlock_fastrlock(lock)
139+
if lock_fastrlock(lock, -1, False):
140+
unlock_fastrlock(lock)
141+
""")
142+
143+
144+
# End of benchmark functions
145+
146+
80147
def threaded(l, test_func, tcount=10):
81148
threads = [ Thread(target=test_func, args=(l,)) for _ in range(tcount) ]
82149
for thread in threads:
@@ -85,7 +152,39 @@ def threaded(l, test_func, tcount=10):
85152
thread.join()
86153

87154

88-
if __name__ == '__main__':
155+
def run_benchmark(name, lock, version, functions, repeat_count, repeat_count_t):
156+
print('Testing %s (%s)' % (name, version))
157+
158+
from timeit import Timer
159+
from functools import partial
160+
161+
print("sequential (x%d):" % repeat_count)
162+
for function in functions:
163+
timer = Timer(partial(function, lock))
164+
print('%-25s: %9.2f msec' % (function.__name__, max(timer.repeat(repeat=4, number=repeat_count)) * 1000.0))
165+
166+
print("threaded 10T (x%d):" % repeat_count_t)
167+
for function in functions:
168+
timer = Timer(partial(threaded, lock, function))
169+
print('%-25s: %9.2f msec' % (function.__name__, max(timer.repeat(repeat=4, number=repeat_count_t)) * 1000.0))
170+
171+
172+
if sys.version_info < (3, 5):
173+
import imp
174+
175+
def load_dynamic(name, module_path):
176+
return imp.load_dynamic(name, module_path)
177+
else:
178+
import importlib.util as _importlib_util
179+
180+
def load_dynamic(name, module_path):
181+
spec = _importlib_util.spec_from_file_location(name, module_path)
182+
module = _importlib_util.module_from_spec(spec)
183+
spec.loader.exec_module(module)
184+
return module
185+
186+
187+
def main():
89188
functions = [
90189
lock_unlock,
91190
reentrant_lock_unlock,
@@ -95,35 +194,78 @@ def threaded(l, test_func, tcount=10):
95194
]
96195

97196
import fastrlock
98-
import sys
99-
from timeit import Timer
100-
from functools import partial
197+
198+
import glob
199+
import os.path
200+
import re
201+
import tempfile
101202

102203
repeat_count = 100000
103204
repeat_count_t = 1000
104205

105-
rlock, flock = ('threading.RLock', RLock(), "%d.%d.%d" % sys.version_info[:3]), ('FastRLock', FLock(), fastrlock.__version__)
206+
rlock = (RLock(), "%d.%d.%d" % sys.version_info[:3])
207+
flock = (FLock(), fastrlock.__version__)
208+
106209
locks = []
107210
args = sys.argv[1:]
108211
if 'rlock' in args:
109212
locks.append(rlock)
110213
if 'flock' in args:
111214
locks.append(flock)
112-
if not locks:
215+
if not args:
113216
locks = [rlock, flock]
217+
114218
for _ in range(args.count('quick')):
115219
repeat_count = max(10, repeat_count // 100)
116220
repeat_count_t = max(5, repeat_count_t // 10)
117221

118-
for name, lock, version in locks:
119-
print('Testing %s (%s)' % (name, version))
222+
for lock, version in locks:
223+
name = type(lock).__name__
224+
run_benchmark(name, lock, version, functions, repeat_count, repeat_count_t)
225+
226+
if 'cython' in args or not args:
227+
lock, version = flock
120228

121-
print("sequential (x%d):" % repeat_count)
122-
for function in functions:
123-
timer = Timer(partial(function, lock))
124-
print('%-25s: %9.2f msec' % (function.__name__, max(timer.repeat(repeat=4, number=repeat_count)) * 1000.0))
229+
from Cython.Build.Cythonize import cython_compile, parse_args
125230

126-
print("threaded 10T (x%d):" % repeat_count_t)
127-
for function in functions:
128-
timer = Timer(partial(threaded, lock, function))
129-
print('%-25s: %9.2f msec' % (function.__name__, max(timer.repeat(repeat=4, number=repeat_count_t)) * 1000.0))
231+
basepath = None
232+
try:
233+
with tempfile.NamedTemporaryFile(mode="w", suffix='.pyx') as f:
234+
code = '\n'.join(cython_code)
235+
cy_function_names = re.findall(r"def (\w+)\(", code)
236+
f.write("from fastrlock.rlock cimport lock_fastrlock, unlock_fastrlock\n\n")
237+
f.write(code)
238+
f.flush()
239+
240+
options, _ = parse_args(["", f.name, "-3", "--force", "--inplace"])
241+
cython_compile(f.name, options)
242+
243+
basepath = os.path.splitext(f.name)[0]
244+
for ext in [".*.so", ".*.pyd", ".so", ".pyd"]:
245+
so_paths = glob.glob(basepath + ext)
246+
if so_paths:
247+
so_path = so_paths[0]
248+
break
249+
else:
250+
print("Failed to find Cython compiled module")
251+
sys.exit(1)
252+
253+
module = load_dynamic(os.path.basename(basepath), so_path)
254+
255+
cy_functions = [getattr(module, name) for name in cy_function_names]
256+
257+
run_benchmark("Cython interface of %s" % type(lock).__name__, lock, version,
258+
cy_functions, repeat_count, repeat_count_t)
259+
260+
finally:
261+
if basepath:
262+
files = glob.glob(basepath)
263+
if len(files) > 3:
264+
print("Found too many artefacts, not deleting temporary files")
265+
else:
266+
for filename in files:
267+
os.unlink(filename)
268+
269+
270+
if __name__ == '__main__':
271+
main()

0 commit comments

Comments
 (0)