From 4a060ddbe2445bfed159bd57e0fa1b9173bc7855 Mon Sep 17 00:00:00 2001 From: Bradlee Speice Date: Sat, 9 Nov 2024 22:06:23 -0500 Subject: [PATCH] Finish converting blog posts --- .../index.mdx | 16 +- blog/2019-12-14-release-the-gil/_article.md | 370 +++++++++++++++++ blog/2019-12-14-release-the-gil/index.mdx | 372 ++++++++++++++++++ blog/2021-08-01-mdx-blog-post.mdx | 48 --- .../_article.md | 60 +++ .../index.mdx | 60 +++ .../video_mp2t.png | Bin 0 -> 48651 bytes 7 files changed, 870 insertions(+), 56 deletions(-) create mode 100644 blog/2019-12-14-release-the-gil/_article.md create mode 100644 blog/2019-12-14-release-the-gil/index.mdx delete mode 100644 blog/2021-08-01-mdx-blog-post.mdx create mode 100644 blog/2022-11-20-webpack-industrial-complex/_article.md create mode 100644 blog/2022-11-20-webpack-industrial-complex/index.mdx create mode 100644 blog/2022-11-20-webpack-industrial-complex/video_mp2t.png diff --git a/blog/2019-09-28-binary-format-shootout/index.mdx b/blog/2019-09-28-binary-format-shootout/index.mdx index 6aad82b..044b4da 100644 --- a/blog/2019-09-28-binary-format-shootout/index.mdx +++ b/blog/2019-09-28-binary-format-shootout/index.mdx @@ -41,7 +41,7 @@ We'll discuss more in detail, but a quick preview of the results: - Flatbuffers: Has some quirks, but largely lived up to its "zero-copy" promises - SBE: Best median and worst-case performance, but the message structure has a limited feature set -# Prologue: Binary Parsing with Nom +## Prologue: Binary Parsing with Nom Our benchmark system will be a simple data processor; given depth-of-book market data from [IEX](https://iextrading.com/trading/market-data/#deep), serialize each message into the schema @@ -119,7 +119,7 @@ Ultimately, because the `nom` code in this shootout was the same for all formats interested in its performance. Still, it's worth mentioning that building the market data parser was actually fun; I didn't have to write tons of boring code by hand. -# Part 1: Cap'n Proto +## Cap'n Proto Now it's time to get into the meaty part of the story. Cap'n Proto was the first format I tried because of how long it has supported Rust (thanks to [dwrensha](https://github.com/dwrensha) for @@ -151,7 +151,7 @@ every read for the segment table. In the end, I put in significant work to make Cap'n Proto as fast as possible, but there were too many issues for me to feel comfortable using it long-term. -# Part 2: Flatbuffers +## Flatbuffers This is the new kid on the block. After a [first attempt](https://github.com/google/flatbuffers/pull/3894) didn't pan out, official support @@ -191,7 +191,7 @@ that tag is nigh on impossible. Ultimately, I enjoyed using Flatbuffers, and had to do significantly less work to make it perform well. -# Part 3: Simple Binary Encoding +## Simple Binary Encoding Support for SBE was added by the author of one of my favorite [Rust blog posts](https://web.archive.org/web/20190427124806/https://polysync.io/blog/session-types-for-hearty-codecs/). @@ -212,7 +212,7 @@ However, if you don't need union types, and can accept that schemas are XML docu worth using. SBE's implementation had the best streaming support of all formats I tested, and doesn't trigger allocation during de/serialization. -# Results +## Results After building a test harness [for](https://github.com/speice-io/marketdata-shootout/blob/master/src/capnp_runner.rs) @@ -225,7 +225,7 @@ the benchmarks, and the raw results are below is the average of 10 runs on a single day of IEX data. Results were validated to make sure that each format parsed the data correctly. -## Serialization +### Serialization This test measures, on a [per-message basis](https://github.com/speice-io/marketdata-shootout/blob/master/src/main.rs#L268-L272), @@ -239,7 +239,7 @@ buffer. | Flatbuffers | 355ns | 2185ns | 3497ns | 14.31s | | SBE | 91ns | 1535ns | 2423ns | 3.91s | -## Deserialization +### Deserialization This test measures, on a [per-message basis](https://github.com/speice-io/marketdata-shootout/blob/master/src/main.rs#L294-L298), @@ -254,7 +254,7 @@ format implementation. | Flatbuffers | 173ns | 421ns | 1007ns | 6.00s | | SBE | 116ns | 286ns | 659ns | 4.05s | -# Conclusion +## Conclusion Building a benchmark turned out to be incredibly helpful in making a decision; because a "union" type isn't important to me, I can be confident that SBE best addresses my needs. diff --git a/blog/2019-12-14-release-the-gil/_article.md b/blog/2019-12-14-release-the-gil/_article.md new file mode 100644 index 0000000..00b47a6 --- /dev/null +++ b/blog/2019-12-14-release-the-gil/_article.md @@ -0,0 +1,370 @@ +--- +layout: post +title: "Release the GIL" +description: "Strategies for Parallelism in Python" +category: +tags: [python] +--- + +Complaining about the [Global Interpreter Lock](https://wiki.python.org/moin/GlobalInterpreterLock) +(GIL) seems like a rite of passage for Python developers. It's easy to criticize a design decision +made before multi-core CPU's were widely available, but the fact that it's still around indicates +that it generally works [Good](https://wiki.c2.com/?PrematureOptimization) +[Enough](https://wiki.c2.com/?YouArentGonnaNeedIt). Besides, there are simple and effective +workarounds; it's not hard to start a +[new process](https://docs.python.org/3/library/multiprocessing.html) and use message passing to +synchronize code running in parallel. + +Still, wouldn't it be nice to have more than a single active interpreter thread? In an age of +asynchronicity and _M:N_ threading, Python seems lacking. The ideal scenario is to take advantage of +both Python's productivity and the modern CPU's parallel capabilities. + +Presented below are two strategies for releasing the GIL's icy grip without giving up on what makes +Python a nice language to start with. Bear in mind: these are just the tools, no claim is made about +whether it's a good idea to use them. Very often, unlocking the GIL is an +[XY problem](https://en.wikipedia.org/wiki/XY_problem); you want application performance, and the +GIL seems like an obvious bottleneck. Remember that any gains from running code in parallel come at +the expense of project complexity; messing with the GIL is ultimately messing with Python's memory +model. + +```python +%load_ext Cython +from numba import jit + +N = 1_000_000_000 +``` + +# Cython + +Put simply, [Cython](https://cython.org/) is a programming language that looks a lot like Python, +gets [transpiled](https://en.wikipedia.org/wiki/Source-to-source_compiler) to C/C++, and integrates +well with the [CPython](https://en.wikipedia.org/wiki/CPython) API. It's great for building Python +wrappers to C and C++ libraries, writing optimized code for numerical processing, and tons more. And +when it comes to managing the GIL, there are two special features: + +- The `nogil` + [function annotation](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#declaring-a-function-as-callable-without-the-gil) + asserts that a Cython function is safe to use without the GIL, and compilation will fail if it + interacts with Python in an unsafe manner +- The `with nogil` + [context manager](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#releasing-the-gil) + explicitly unlocks the CPython GIL while active + +Whenever Cython code runs inside a `with nogil` block on a separate thread, the Python interpreter +is unblocked and allowed to continue work elsewhere. We'll define a "busy work" function that +demonstrates this principle in action: + +```python +%%cython + +# Annotating a function with `nogil` indicates only that it is safe +# to call in a `with nogil` block. It *does not* release the GIL. +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): + # Explicitly release the GIL while running `fibonacci` + with nogil: + value = fibonacci(n) + + return value + + +def cython_gil(unsigned long n): + # Because the GIL is not explicitly released, it implicitly + # remains acquired when running the `fibonacci` function + return fibonacci(n) +``` + +First, let's time how long it takes Cython to calculate the billionth Fibonacci number: + +```python +%%time +_ = cython_gil(N); +``` + +>
+> CPU times: user 365 ms, sys: 0 ns, total: 365 ms
+> Wall time: 372 ms
+> 
+ +```python +%%time +_ = cython_nogil(N); +``` + +>
+> CPU times: user 381 ms, sys: 0 ns, total: 381 ms
+> Wall time: 388 ms
+> 
+ +Both versions (with and without GIL) take effectively the same amount of time to run. Even when +running this calculation in parallel on separate threads, it is expected that the run time will +double because only one thread can be active at a time: + +```python +%%time +from threading import Thread + +# Create the two threads to run on +t1 = Thread(target=cython_gil, args=[N]) +t2 = Thread(target=cython_gil, args=[N]) +# Start the threads +t1.start(); t2.start() +# Wait for the threads to finish +t1.join(); t2.join() +``` + +>
+> CPU times: user 641 ms, sys: 5.62 ms, total: 647 ms
+> Wall time: 645 ms
+> 
+ +However, if the first thread releases the GIL, the second thread is free to acquire it and run in +parallel: + +```python +%%time + +t1 = Thread(target=cython_nogil, args=[N]) +t2 = Thread(target=cython_gil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 717 ms, sys: 372 µs, total: 718 ms
+> Wall time: 358 ms
+> 
+ +Because `user` time represents the sum of processing time on all threads, it doesn't change much. +The ["wall time"](https://en.wikipedia.org/wiki/Elapsed_real_time) has been cut roughly in half +because each function is running simultaneously. + +Keep in mind that the **order in which threads are started** makes a difference! + +```python +%%time + +# Note that the GIL-locked version is started first +t1 = Thread(target=cython_gil, args=[N]) +t2 = Thread(target=cython_nogil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 667 ms, sys: 0 ns, total: 667 ms
+> Wall time: 672 ms
+> 
+ +Even though the second thread releases the GIL while running, it can't start until the first has +completed. Thus, the overall runtime is effectively the same as running two GIL-locked threads. + +Finally, be aware that attempting to unlock the GIL from a thread that doesn't own it will crash the +**interpreter**, not just the thread attempting the unlock: + +```python +%%cython + +cdef int cython_recurse(int n) nogil: + if n <= 0: + return 0 + + with nogil: + return cython_recurse(n - 1) + +cython_recurse(2) +``` + +>
+> Fatal Python error: PyEval_SaveThread: NULL tstate
+> 
+> Thread 0x00007f499effd700 (most recent call first):
+>   File "/home/bspeice/.virtualenvs/release-the-gil/lib/python3.7/site-packages/ipykernel/parentpoller.py", line 39 in run
+>   File "/usr/lib/python3.7/threading.py", line 926 in _bootstrap_inner
+>   File "/usr/lib/python3.7/threading.py", line 890 in _bootstrap
+> 
+ +In practice, avoiding this issue is simple. First, `nogil` functions probably shouldn't contain +`with nogil` blocks. Second, Cython can +[conditionally acquire/release](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#conditional-acquiring-releasing-the-gil) +the GIL, so these conditions can be used to synchronize access. Finally, Cython's documentation for +[external C code](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#acquiring-and-releasing-the-gil) +contains more detail on how to safely manage the GIL. + +To conclude: use Cython's `nogil` annotation to assert that functions are safe for calling when the +GIL is unlocked, and `with nogil` to actually unlock the GIL and run those functions. + +# Numba + +Like Cython, [Numba](https://numba.pydata.org/) is a "compiled Python." Where Cython works by +compiling a Python-like language to C/C++, Numba compiles Python bytecode _directly to machine code_ +at runtime. Behavior is controlled with a special `@jit` decorator; calling a decorated function +first compiles it to machine code before running. Calling the function a second time re-uses that +machine code unless the argument types have changed. + +Numba works best when a `nopython=True` argument is added to the `@jit` decorator; functions +compiled in [`nopython`](http://numba.pydata.org/numba-doc/latest/user/jit.html?#nopython) mode +avoid the CPython API and have performance comparable to C. Further, adding `nogil=True` to the +`@jit` decorator unlocks the GIL while that function is running. Note that `nogil` and `nopython` +are separate arguments; while it is necessary for code to be compiled in `nopython` mode in order to +release the lock, the GIL will remain locked if `nogil=False` (the default). + +Let's repeat the same experiment, this time using Numba instead of Cython: + +```python +# The `int` type annotation is only for humans and is ignored +# by Numba. +@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 + + +# Run using `nopython` mode to receive a performance boost, +# but GIL remains locked due to `nogil=False` by default. +@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 + + +# Call each function once to force compilation; we don't want +# the timing statistics to include how long it takes to compile. +numba_nogil(N) +numba_gil(N); +``` + +We'll perform the same tests as above; first, figure out how long it takes the function to run: + +```python +%%time +_ = numba_gil(N) +``` + +>
+> CPU times: user 253 ms, sys: 258 µs, total: 253 ms
+> Wall time: 251 ms
+> 
+ + +Aside: it's not immediately clear why Numba takes ~20% less time to run than Cython for code that should be +effectively identical after compilation. + + +When running two GIL-locked threads, the result (as expected) takes around twice as long to compute: + +```python +%%time +t1 = Thread(target=numba_gil, args=[N]) +t2 = Thread(target=numba_gil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 541 ms, sys: 3.96 ms, total: 545 ms
+> Wall time: 541 ms
+> 
+ +But if the GIL-unlocking thread starts first, both threads run in parallel: + +```python +%%time +t1 = Thread(target=numba_nogil, args=[N]) +t2 = Thread(target=numba_gil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 551 ms, sys: 7.77 ms, total: 559 ms
+> Wall time: 279 ms
+> 
+ +Just like Cython, starting the GIL-locked thread first leads to poor performance: + +```python +%%time +t1 = Thread(target=numba_gil, args=[N]) +t2 = Thread(target=numba_nogil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 524 ms, sys: 0 ns, total: 524 ms
+> Wall time: 522 ms
+> 
+ +Finally, unlike Cython, Numba will unlock the GIL if and only if it is currently acquired; +recursively calling `@jit(nogil=True)` functions is perfectly safe: + +```python +from numba import jit + +@jit(nopython=True, nogil=True) +def numba_recurse(n: int) -> int: + if n <= 0: + return 0 + + return numba_recurse(n - 1) + +numba_recurse(2); +``` + +# Conclusion + +Before finishing, it's important to address pain points that will show up if these techniques are +used in a more realistic project: + +First, code running in a GIL-free context will likely also need non-trivial data structures; +GIL-free functions aren't useful if they're constantly interacting with Python objects whose access +requires the GIL. Cython provides +[extension types](http://docs.cython.org/en/latest/src/tutorial/cdef_classes.html) and Numba +provides a [`@jitclass`](https://numba.pydata.org/numba-doc/dev/user/jitclass.html) decorator to +address this need. + +Second, building and distributing applications that make use of Cython/Numba can be complicated. +Cython packages require running the compiler, (potentially) linking/packaging external dependencies, +and distributing a binary wheel. Numba is generally simpler because the code being distributed is +pure Python, but can be tricky since errors aren't detected until runtime. + +Finally, while unlocking the GIL is often a solution in search of a problem, both Cython and Numba +provide tools to directly manage the GIL when appropriate. This enables true parallelism (not just +[concurrency](https://stackoverflow.com/a/1050257)) that is impossible in vanilla Python. diff --git a/blog/2019-12-14-release-the-gil/index.mdx b/blog/2019-12-14-release-the-gil/index.mdx new file mode 100644 index 0000000..f0683c2 --- /dev/null +++ b/blog/2019-12-14-release-the-gil/index.mdx @@ -0,0 +1,372 @@ +--- +slug: 2019/12/release-the-gil +title: Release the GIL +date: 2019-12-14 12:00:00 +authors: [bspeice] +tags: [] +--- + +Complaining about the [Global Interpreter Lock](https://wiki.python.org/moin/GlobalInterpreterLock) +(GIL) seems like a rite of passage for Python developers. It's easy to criticize a design decision +made before multi-core CPU's were widely available, but the fact that it's still around indicates +that it generally works [Good](https://wiki.c2.com/?PrematureOptimization) +[Enough](https://wiki.c2.com/?YouArentGonnaNeedIt). Besides, there are simple and effective +workarounds; it's not hard to start a +[new process](https://docs.python.org/3/library/multiprocessing.html) and use message passing to +synchronize code running in parallel. + +Still, wouldn't it be nice to have more than a single active interpreter thread? In an age of +asynchronicity and _M:N_ threading, Python seems lacking. The ideal scenario is to take advantage of +both Python's productivity and the modern CPU's parallel capabilities. + + + +Presented below are two strategies for releasing the GIL's icy grip without giving up on what makes +Python a nice language to start with. Bear in mind: these are just the tools, no claim is made about +whether it's a good idea to use them. Very often, unlocking the GIL is an +[XY problem](https://en.wikipedia.org/wiki/XY_problem); you want application performance, and the +GIL seems like an obvious bottleneck. Remember that any gains from running code in parallel come at +the expense of project complexity; messing with the GIL is ultimately messing with Python's memory +model. + +```python +%load_ext Cython +from numba import jit + +N = 1_000_000_000 +``` + +## Cython + +Put simply, [Cython](https://cython.org/) is a programming language that looks a lot like Python, +gets [transpiled](https://en.wikipedia.org/wiki/Source-to-source_compiler) to C/C++, and integrates +well with the [CPython](https://en.wikipedia.org/wiki/CPython) API. It's great for building Python +wrappers to C and C++ libraries, writing optimized code for numerical processing, and tons more. And +when it comes to managing the GIL, there are two special features: + +- The `nogil` + [function annotation](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#declaring-a-function-as-callable-without-the-gil) + asserts that a Cython function is safe to use without the GIL, and compilation will fail if it + interacts with Python in an unsafe manner +- The `with nogil` + [context manager](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#releasing-the-gil) + explicitly unlocks the CPython GIL while active + +Whenever Cython code runs inside a `with nogil` block on a separate thread, the Python interpreter +is unblocked and allowed to continue work elsewhere. We'll define a "busy work" function that +demonstrates this principle in action: + +```python +%%cython + +# Annotating a function with `nogil` indicates only that it is safe +# to call in a `with nogil` block. It *does not* release the GIL. +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): + # Explicitly release the GIL while running `fibonacci` + with nogil: + value = fibonacci(n) + + return value + + +def cython_gil(unsigned long n): + # Because the GIL is not explicitly released, it implicitly + # remains acquired when running the `fibonacci` function + return fibonacci(n) +``` + +First, let's time how long it takes Cython to calculate the billionth Fibonacci number: + +```python +%%time +_ = cython_gil(N); +``` + +>
+> CPU times: user 365 ms, sys: 0 ns, total: 365 ms
+> Wall time: 372 ms
+> 
+ +```python +%%time +_ = cython_nogil(N); +``` + +>
+> CPU times: user 381 ms, sys: 0 ns, total: 381 ms
+> Wall time: 388 ms
+> 
+ +Both versions (with and without GIL) take effectively the same amount of time to run. Even when +running this calculation in parallel on separate threads, it is expected that the run time will +double because only one thread can be active at a time: + +```python +%%time +from threading import Thread + +# Create the two threads to run on +t1 = Thread(target=cython_gil, args=[N]) +t2 = Thread(target=cython_gil, args=[N]) +# Start the threads +t1.start(); t2.start() +# Wait for the threads to finish +t1.join(); t2.join() +``` + +>
+> CPU times: user 641 ms, sys: 5.62 ms, total: 647 ms
+> Wall time: 645 ms
+> 
+ +However, if the first thread releases the GIL, the second thread is free to acquire it and run in +parallel: + +```python +%%time + +t1 = Thread(target=cython_nogil, args=[N]) +t2 = Thread(target=cython_gil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 717 ms, sys: 372 µs, total: 718 ms
+> Wall time: 358 ms
+> 
+ +Because `user` time represents the sum of processing time on all threads, it doesn't change much. +The ["wall time"](https://en.wikipedia.org/wiki/Elapsed_real_time) has been cut roughly in half +because each function is running simultaneously. + +Keep in mind that the **order in which threads are started** makes a difference! + +```python +%%time + +# Note that the GIL-locked version is started first +t1 = Thread(target=cython_gil, args=[N]) +t2 = Thread(target=cython_nogil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 667 ms, sys: 0 ns, total: 667 ms
+> Wall time: 672 ms
+> 
+ +Even though the second thread releases the GIL while running, it can't start until the first has +completed. Thus, the overall runtime is effectively the same as running two GIL-locked threads. + +Finally, be aware that attempting to unlock the GIL from a thread that doesn't own it will crash the +**interpreter**, not just the thread attempting the unlock: + +```python +%%cython + +cdef int cython_recurse(int n) nogil: + if n <= 0: + return 0 + + with nogil: + return cython_recurse(n - 1) + +cython_recurse(2) +``` + +>
+> Fatal Python error: PyEval_SaveThread: NULL tstate
+> 
+> Thread 0x00007f499effd700 (most recent call first):
+>   File "/home/bspeice/.virtualenvs/release-the-gil/lib/python3.7/site-packages/ipykernel/parentpoller.py", line 39 in run
+>   File "/usr/lib/python3.7/threading.py", line 926 in _bootstrap_inner
+>   File "/usr/lib/python3.7/threading.py", line 890 in _bootstrap
+> 
+ +In practice, avoiding this issue is simple. First, `nogil` functions probably shouldn't contain +`with nogil` blocks. Second, Cython can +[conditionally acquire/release](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#conditional-acquiring-releasing-the-gil) +the GIL, so these conditions can be used to synchronize access. Finally, Cython's documentation for +[external C code](https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#acquiring-and-releasing-the-gil) +contains more detail on how to safely manage the GIL. + +To conclude: use Cython's `nogil` annotation to assert that functions are safe for calling when the +GIL is unlocked, and `with nogil` to actually unlock the GIL and run those functions. + +## Numba + +Like Cython, [Numba](https://numba.pydata.org/) is a "compiled Python." Where Cython works by +compiling a Python-like language to C/C++, Numba compiles Python bytecode _directly to machine code_ +at runtime. Behavior is controlled with a special `@jit` decorator; calling a decorated function +first compiles it to machine code before running. Calling the function a second time re-uses that +machine code unless the argument types have changed. + +Numba works best when a `nopython=True` argument is added to the `@jit` decorator; functions +compiled in [`nopython`](http://numba.pydata.org/numba-doc/latest/user/jit.html?#nopython) mode +avoid the CPython API and have performance comparable to C. Further, adding `nogil=True` to the +`@jit` decorator unlocks the GIL while that function is running. Note that `nogil` and `nopython` +are separate arguments; while it is necessary for code to be compiled in `nopython` mode in order to +release the lock, the GIL will remain locked if `nogil=False` (the default). + +Let's repeat the same experiment, this time using Numba instead of Cython: + +```python +# The `int` type annotation is only for humans and is ignored +# by Numba. +@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 + + +# Run using `nopython` mode to receive a performance boost, +# but GIL remains locked due to `nogil=False` by default. +@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 + + +# Call each function once to force compilation; we don't want +# the timing statistics to include how long it takes to compile. +numba_nogil(N) +numba_gil(N); +``` + +We'll perform the same tests as above; first, figure out how long it takes the function to run: + +```python +%%time +_ = numba_gil(N) +``` + +>
+> CPU times: user 253 ms, sys: 258 µs, total: 253 ms
+> Wall time: 251 ms
+> 
+ + +Aside: it's not immediately clear why Numba takes ~20% less time to run than Cython for code that should be +effectively identical after compilation. + + +When running two GIL-locked threads, the result (as expected) takes around twice as long to compute: + +```python +%%time +t1 = Thread(target=numba_gil, args=[N]) +t2 = Thread(target=numba_gil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 541 ms, sys: 3.96 ms, total: 545 ms
+> Wall time: 541 ms
+> 
+ +But if the GIL-unlocking thread starts first, both threads run in parallel: + +```python +%%time +t1 = Thread(target=numba_nogil, args=[N]) +t2 = Thread(target=numba_gil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 551 ms, sys: 7.77 ms, total: 559 ms
+> Wall time: 279 ms
+> 
+ +Just like Cython, starting the GIL-locked thread first leads to poor performance: + +```python +%%time +t1 = Thread(target=numba_gil, args=[N]) +t2 = Thread(target=numba_nogil, args=[N]) +t1.start(); t2.start() +t1.join(); t2.join() +``` + +>
+> CPU times: user 524 ms, sys: 0 ns, total: 524 ms
+> Wall time: 522 ms
+> 
+ +Finally, unlike Cython, Numba will unlock the GIL if and only if it is currently acquired; +recursively calling `@jit(nogil=True)` functions is perfectly safe: + +```python +from numba import jit + +@jit(nopython=True, nogil=True) +def numba_recurse(n: int) -> int: + if n <= 0: + return 0 + + return numba_recurse(n - 1) + +numba_recurse(2); +``` + +## Conclusion + +Before finishing, it's important to address pain points that will show up if these techniques are +used in a more realistic project: + +First, code running in a GIL-free context will likely also need non-trivial data structures; +GIL-free functions aren't useful if they're constantly interacting with Python objects whose access +requires the GIL. Cython provides +[extension types](http://docs.cython.org/en/latest/src/tutorial/cdef_classes.html) and Numba +provides a [`@jitclass`](https://numba.pydata.org/numba-doc/dev/user/jitclass.html) decorator to +address this need. + +Second, building and distributing applications that make use of Cython/Numba can be complicated. +Cython packages require running the compiler, (potentially) linking/packaging external dependencies, +and distributing a binary wheel. Numba is generally simpler because the code being distributed is +pure Python, but can be tricky since errors aren't detected until runtime. + +Finally, while unlocking the GIL is often a solution in search of a problem, both Cython and Numba +provide tools to directly manage the GIL when appropriate. This enables true parallelism (not just +[concurrency](https://stackoverflow.com/a/1050257)) that is impossible in vanilla Python. diff --git a/blog/2021-08-01-mdx-blog-post.mdx b/blog/2021-08-01-mdx-blog-post.mdx deleted file mode 100644 index db4736c..0000000 --- a/blog/2021-08-01-mdx-blog-post.mdx +++ /dev/null @@ -1,48 +0,0 @@ ---- -slug: mdx-blog-post -title: MDX Blog Post With An Extraordinarily Long Title -date: 2021-08-02 10:00:00 -authors: [bspeice] -tags: [] ---- - -## title - -Hello? - -Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/). - -
- Hello - - Testing - {1 + 2} -
- - -:::tip - -Use the power of React to create interactive blog posts. - -::: - -{/* truncate */} - -For example, use JSX to create an interactive button: - -```js - - -``` - -```cpp -class MyClass { -public: - MyClass() = default; -}; - -int main() { - auto x = 24; -} -``` - - diff --git a/blog/2022-11-20-webpack-industrial-complex/_article.md b/blog/2022-11-20-webpack-industrial-complex/_article.md new file mode 100644 index 0000000..33fe67a --- /dev/null +++ b/blog/2022-11-20-webpack-industrial-complex/_article.md @@ -0,0 +1,60 @@ +--- +layout: post +title: "The webpack industrial complex" +description: "Reflections on a new project" +category: +tags: [webpack, react, vite] +--- + +This started because I wanted to build a synthesizer. Setting a goal of "digital DX7" was ambitious, but I needed something unrelated to the day job. Beyond that, working with audio seemed like a good challenge. I enjoy performance-focused code, and performance problems in audio are conspicuous. Building a web project was an obvious choice because of the web audio API documentation and independence from a large Digital Audio Workstation (DAW). + +The project was soon derailed trying to sort out technical issues unrelated to the original purpose. Finding a resolution was a frustrating journey, and it's still not clear whether those problems were my fault. As a result, I'm writing this to try making sense of it, as a case study/reference material, and to salvage something from the process. + +## Starting strong + +The sole starting requirement was to write everything in TypeScript. Not because of project scale, but because guardrails help with unfamiliar territory. Keeping that in mind, the first question was: how does one start a new project? All I actually need is "compile TypeScript, show it in a browser." + +Create React App (CRA) came to the rescue and the rest of that evening was a joy. My TypeScript/JavaScript skills were rusty, but the online documentation was helpful. I had never understood the appeal of JSX (why put a DOM in JavaScript?) until it made connecting an `onEvent` handler and a function easy. + +Some quick dimensional analysis later and there was a sine wave oscillator playing A=440 through the speakers. I specifically remember thinking "modern browsers are magical." + +## Continuing on + +Now comes the first mistake: I began to worry about "scale" before encountering an actual problem. Rather than rendering audio in the main thread, why not use audio worklets and render in a background thread instead? + +The first sign something was amiss came from the TypeScript compiler errors showing the audio worklet API [was missing](https://github.com/microsoft/TypeScript/issues/28308). After searching out Github issues and (unsuccessfully) tweaking the `.tsconfig` settings, I settled on installing a package and moving on. + +The next problem came from actually using the API. Worklets must load from separate "modules," but it wasn't clear how to guarantee the worklet code stayed separate from the application. I saw recommendations to use `new URL(, import.meta.url)` and it worked! Well, kind of: + +![Browser error](/assets/images/2022-11-20-video_mp2t.png) + +That file has the audio processor code, so why does it get served with `Content-Type: video/mp2t`? + +## Floundering about + +Now comes the second mistake: even though I didn't understand the error, I ignored recommendations to [just use JavaScript](https://hackernoon.com/implementing-audioworklets-with-react-8a80a470474) and stuck by the original TypeScript requirement. + +I tried different project structures. Moving the worklet code to a new folder didn't help, nor did setting up a monorepo and placing it in a new package. + +I tried three different CRA tools - `react-app-rewired`, `craco`, `customize-react-app` - but got the same problem. Each has varying levels of compatibility with recent CRA versions, so it wasn't clear if I had the right solution but implemented it incorrectly. After attempting to eject the application and panicking after seeing the configuration, I abandoned that as well. + +I tried changing the webpack configuration: using [new](https://github.com/webpack/webpack/issues/11543#issuecomment-917673256) [loaders](https://github.com/popelenkow/worker-url), setting [asset rules](https://github.com/webpack/webpack/discussions/14093#discussioncomment-1257149), even [changing how webpack detects worker resources](https://github.com/webpack/webpack/issues/11543#issuecomment-826897590). In hindsight, entry points may have been the answer. But because CRA actively resists attempts to change its webpack configuration, and I couldn't find audio worklet examples in any other framework, I gave up. + +I tried so many application frameworks. Next.js looked like a good candidate, but added its own [bespoke webpack complexity](https://github.com/vercel/next.js/issues/24907) to the existing confusion. Astro had the best "getting started" experience, but I refuse to install an IDE-specific plugin. I first used Deno while exploring Lume, but it couldn't import the audio worklet types (maybe because of module compatibility?). Each framework was unique in its own way (shout-out to SvelteKit) but I couldn't figure out how to make them work. + +## Learning and reflecting + +I ended up using Vite and vite-plugin-react-pages to handle both "build the app" and "bundle worklets," but the specific tool choice isn't important. Instead, the focus should be on lessons learned. + +For myself: + +- I'm obsessed with tooling, to the point it can derail the original goal. While it comes from a good place (for example: "types are awesome"), it can get in the way of more important work +- I tend to reach for online resources right after seeing a new problem. While finding help online is often faster, spending time understanding the problem would have been more productive than cycling through (often outdated) blog posts + +For the tools: + +- Resource bundling is great and solves a genuine challenge. I've heard too many horror stories of developers writing modules by hand to believe this is unnecessary complexity +- Webpack is a build system and modern frameworks are deeply dependent on it (hence the "webpack industrial complex"). While this often saves users from unnecessary complexity, there's no path forward if something breaks +- There's little ability to mix and match tools across frameworks. Next.js and Gatsby let users extend webpack, but because each framework adds its own modules, changes aren't portable. After spending a week looking at webpack, I had an example running with parcel in thirty minutes, but couldn't integrate it + +In the end, learning new systems is fun, but a focus on tools that "just work" can leave users out in the cold if they break down. \ No newline at end of file diff --git a/blog/2022-11-20-webpack-industrial-complex/index.mdx b/blog/2022-11-20-webpack-industrial-complex/index.mdx new file mode 100644 index 0000000..109cd4a --- /dev/null +++ b/blog/2022-11-20-webpack-industrial-complex/index.mdx @@ -0,0 +1,60 @@ +--- +slug: 2011/11/webpack-industrial-complex +title: The webpack industrial complex +date: 2022-11-20 12:00:00 +authors: [bspeice] +tags: [] +--- + +This started because I wanted to build a synthesizer. Setting a goal of "digital DX7" was ambitious, but I needed something unrelated to the day job. Beyond that, working with audio seemed like a good challenge. I enjoy performance-focused code, and performance problems in audio are conspicuous. Building a web project was an obvious choice because of the web audio API documentation and independence from a large Digital Audio Workstation (DAW). + +The project was soon derailed trying to sort out technical issues unrelated to the original purpose. Finding a resolution was a frustrating journey, and it's still not clear whether those problems were my fault. As a result, I'm writing this to try making sense of it, as a case study/reference material, and to salvage something from the process. + +## Starting strong + +The sole starting requirement was to write everything in TypeScript. Not because of project scale, but because guardrails help with unfamiliar territory. Keeping that in mind, the first question was: how does one start a new project? All I actually need is "compile TypeScript, show it in a browser." + +Create React App (CRA) came to the rescue and the rest of that evening was a joy. My TypeScript/JavaScript skills were rusty, but the online documentation was helpful. I had never understood the appeal of JSX (why put a DOM in JavaScript?) until it made connecting an `onEvent` handler and a function easy. + +Some quick dimensional analysis later and there was a sine wave oscillator playing A=440 through the speakers. I specifically remember thinking "modern browsers are magical." + +## Continuing on + +Now comes the first mistake: I began to worry about "scale" before encountering an actual problem. Rather than rendering audio in the main thread, why not use audio worklets and render in a background thread instead? + +The first sign something was amiss came from the TypeScript compiler errors showing the audio worklet API [was missing](https://github.com/microsoft/TypeScript/issues/28308). After searching out Github issues and (unsuccessfully) tweaking the `.tsconfig` settings, I settled on installing a package and moving on. + +The next problem came from actually using the API. Worklets must load from separate "modules," but it wasn't clear how to guarantee the worklet code stayed separate from the application. I saw recommendations to use `new URL(, import.meta.url)` and it worked! Well, kind of: + +![Browser error](./video_mp2t.png) + +That file has the audio processor code, so why does it get served with `Content-Type: video/mp2t`? + +## Floundering about + +Now comes the second mistake: even though I didn't understand the error, I ignored recommendations to [just use JavaScript](https://hackernoon.com/implementing-audioworklets-with-react-8a80a470474) and stuck by the original TypeScript requirement. + +I tried different project structures. Moving the worklet code to a new folder didn't help, nor did setting up a monorepo and placing it in a new package. + +I tried three different CRA tools - `react-app-rewired`, `craco`, `customize-react-app` - but got the same problem. Each has varying levels of compatibility with recent CRA versions, so it wasn't clear if I had the right solution but implemented it incorrectly. After attempting to eject the application and panicking after seeing the configuration, I abandoned that as well. + +I tried changing the webpack configuration: using [new](https://github.com/webpack/webpack/issues/11543#issuecomment-917673256) [loaders](https://github.com/popelenkow/worker-url), setting [asset rules](https://github.com/webpack/webpack/discussions/14093#discussioncomment-1257149), even [changing how webpack detects worker resources](https://github.com/webpack/webpack/issues/11543#issuecomment-826897590). In hindsight, entry points may have been the answer. But because CRA actively resists attempts to change its webpack configuration, and I couldn't find audio worklet examples in any other framework, I gave up. + +I tried so many application frameworks. Next.js looked like a good candidate, but added its own [bespoke webpack complexity](https://github.com/vercel/next.js/issues/24907) to the existing confusion. Astro had the best "getting started" experience, but I refuse to install an IDE-specific plugin. I first used Deno while exploring Lume, but it couldn't import the audio worklet types (maybe because of module compatibility?). Each framework was unique in its own way (shout-out to SvelteKit) but I couldn't figure out how to make them work. + +## Learning and reflecting + +I ended up using Vite and vite-plugin-react-pages to handle both "build the app" and "bundle worklets," but the specific tool choice isn't important. Instead, the focus should be on lessons learned. + +For myself: + +- I'm obsessed with tooling, to the point it can derail the original goal. While it comes from a good place (for example: "types are awesome"), it can get in the way of more important work +- I tend to reach for online resources right after seeing a new problem. While finding help online is often faster, spending time understanding the problem would have been more productive than cycling through (often outdated) blog posts + +For the tools: + +- Resource bundling is great and solves a genuine challenge. I've heard too many horror stories of developers writing modules by hand to believe this is unnecessary complexity +- Webpack is a build system and modern frameworks are deeply dependent on it (hence the "webpack industrial complex"). While this often saves users from unnecessary complexity, there's no path forward if something breaks +- There's little ability to mix and match tools across frameworks. Next.js and Gatsby let users extend webpack, but because each framework adds its own modules, changes aren't portable. After spending a week looking at webpack, I had an example running with parcel in thirty minutes, but couldn't integrate it + +In the end, learning new systems is fun, but a focus on tools that "just work" can leave users out in the cold if they break down. \ No newline at end of file diff --git a/blog/2022-11-20-webpack-industrial-complex/video_mp2t.png b/blog/2022-11-20-webpack-industrial-complex/video_mp2t.png new file mode 100644 index 0000000000000000000000000000000000000000..04e7d86b93663820dd349d5eb44d9304484d1bf6 GIT binary patch literal 48651 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 CYn`P4 literal 0 HcmV?d00001