From 29edf9cc8f3f453bb04056179c2d07405259e33c Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Mon, 25 Nov 2019 23:06:53 -0500 Subject: [PATCH] Fibonacci and double-unlock examples --- .gitignore | 2 + setup.py | 19 +++++++ src/__init__.py | 0 src/double_unlock.py | 4 ++ src/double_unlock_cython.pyx | 8 +++ src/fibonacci.py | 102 +++++++++++++++++++++++++++++++++++ src/fibonacci_cython.pyx | 24 +++++++++ 7 files changed, 159 insertions(+) create mode 100644 .gitignore create mode 100644 setup.py create mode 100644 src/__init__.py create mode 100644 src/double_unlock.py create mode 100644 src/double_unlock_cython.pyx create mode 100644 src/fibonacci.py create mode 100644 src/fibonacci_cython.pyx diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7858828 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.c +*.so \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..2a174ed --- /dev/null +++ b/setup.py @@ -0,0 +1,19 @@ +from setuptools import setup, find_packages +from Cython.Build import cythonize + + +setup( + name="release-the-gil", + version="0.1", + author="Bradlee Speice", + author_email="bradlee@speice.io", + description="Basic examples of parallelism in Python", + url="https://github.com/speice-io/release-the-gil", + packages=find_packages(), + ext_modules=cythonize("src/*.pyx"), + install_requires=[ + "Cython", + "numba", + "texttable" + ], +) \ No newline at end of file diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/double_unlock.py b/src/double_unlock.py new file mode 100644 index 0000000..c0b4b96 --- /dev/null +++ b/src/double_unlock.py @@ -0,0 +1,4 @@ +from double_unlock_cython import unlock + +if __name__ == '__main__': + unlock() diff --git a/src/double_unlock_cython.pyx b/src/double_unlock_cython.pyx new file mode 100644 index 0000000..d3dc66b --- /dev/null +++ b/src/double_unlock_cython.pyx @@ -0,0 +1,8 @@ +cdef void _unlock() nogil: + with nogil: + pass + + +def unlock(): + with nogil: + _unlock() diff --git a/src/fibonacci.py b/src/fibonacci.py new file mode 100644 index 0000000..d0b438b --- /dev/null +++ b/src/fibonacci.py @@ -0,0 +1,102 @@ +import argparse +from collections import defaultdict +from threading import Thread +from time import monotonic_ns +from typing import List, DefaultDict + +from numba import jit +from texttable import Texttable + +from fibonacci_cython import cython_gil, cython_nogil + + +@jit(nopython=True, nogil=True) +def numba_nogil(n: int) -> int: + if n <= 1: + return n + + a = 0 + b = 1 + + c = a + b + for _i in range(2, n): + a = b + b = c + c = a + b + + return c + + +@jit(nopython=True) +def numba_gil(n: int) -> int: + if n <= 1: + return n + + a = 0 + b = 1 + + c = a + b + for _i in range(2, n): + a = b + b = c + c = a + b + + return c + + +def main(n: int = 1_000_000_000): + # Pre-compile the numba variants + numba_nogil(15) + numba_gil(15) + + functions = [cython_gil, cython_nogil, numba_gil, numba_nogil] + names = ["cython_gil", "cython_nogil", "numba_gil", "numba_nogil"] + results_single: List[str] = [] + results: DefaultDict[str, List[str]] = defaultdict(list) + + for i, t1_function in enumerate(functions): + t1_name = names[i] + + start = monotonic_ns() + t1_function(n) + end = monotonic_ns() + runtime = str((end - start) / float(1_000_000)) + "ms" + results_single.append(runtime) + + for j, t2_function in enumerate(functions): + t1 = Thread(target=t1_function, args=[n]) + t2 = Thread(target=t2_function, args=[n]) + + # While there's overhead in the thread start/join calls unrelated to + # actual runtime, it's pretty small relative to total runtime + start = monotonic_ns() + + # The order in which we start threads matters! + t1.start() + t2.start() + t1.join() + t2.join() + end = monotonic_ns() + + runtime = str((end - start) / float(1_000_000)) + "ms" + results[t1_name].append(runtime) + + table = Texttable() + table.header(names) + table.add_row(results_single) + print(table.draw()) + + table = Texttable() + table.header([""] + names) + for main_name, results in results.items(): + table.add_row([main_name] + results) + + print(table.draw()) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('-n', help='Fibonacci number to calculate', default=1_000_000_000) + cmdline = parser.parse_args() + + main(cmdline.n) diff --git a/src/fibonacci_cython.pyx b/src/fibonacci_cython.pyx new file mode 100644 index 0000000..08755f1 --- /dev/null +++ b/src/fibonacci_cython.pyx @@ -0,0 +1,24 @@ +cdef unsigned long fibonacci(unsigned long n) nogil: + if n <= 1: + return n + + cdef unsigned long a = 0, b = 1, c = 0 + + c = a + b + for _i in range(2, n): + a = b + b = c + c = a + b + + return c + + +def cython_nogil(unsigned long n): + with nogil: + value = fibonacci(n) + + return value + + +def cython_gil(unsigned long n): + return fibonacci(n)