Skip to main content

The webpack industrial complex

· 5 min read
Bradlee Speice

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.

Release the GIL

· 9 min read
Bradlee Speice

Complaining about the Global Interpreter Lock (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 Enough. Besides, there are simple and effective workarounds; it's not hard to start a new process 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.

Binary format shootout

· 9 min read
Bradlee Speice

I've found that in many personal projects, analysis paralysis is particularly deadly. Making good decisions in the beginning avoids pain and suffering later; if extra research prevents future problems, I'm happy to continue procrastinating researching indefinitely.

So let's say you're in need of a binary serialization format. Data will be going over the network, not just in memory, so having a schema document and code generation is a must. Performance is crucial, so formats that support zero-copy de/serialization are given priority. And the more languages supported, the better; I use Rust, but can't predict what other languages this could interact with.

Given these requirements, the candidates I could find were:

On building high performance systems

· 13 min read
Bradlee Speice

Prior to working in the trading industry, my assumption was that High Frequency Trading (HFT) is made up of people who have access to secret techniques mortal developers could only dream of. There had to be some secret art that could only be learned if one had an appropriately tragic backstory.

Making bread

· 2 min read
Bradlee Speice

Having recently started my "gardening leave" between positions, I have some more personal time available. I'm planning to stay productive, contributing to some open-source projects, but it also occurred to me that despite talking about bread pics, this blog has been purely technical. Maybe I'll change the site title from "The Old Speice Guy" to "Bites and Bytes"?

Allocations in Rust: Summary

· 2 min read
Bradlee Speice

While there's a lot of interesting detail captured in this series, it's often helpful to have a document that answers some "yes/no" questions. You may not care about what an Iterator looks like in assembly, you just need to know whether it allocates an object on the heap or not. And while Rust will prioritize the fastest behavior it can, here are the rules for each memory type:

Allocations in Rust: Compiler optimizations

· 4 min read

Up to this point, we've been discussing memory usage in the Rust language by focusing on simple rules that are mostly right for small chunks of code. We've spent time showing how those rules work themselves out in practice, and become familiar with reading the assembly code needed to see each memory type (global, stack, heap) in action.

Throughout the series so far, we've put a handicap on the code. In the name of consistent and understandable results, we've asked the compiler to pretty please leave the training wheels on. Now is the time where we throw out all the rules and take off the kid gloves. As it turns out, both the Rust compiler and the LLVM optimizers are incredibly sophisticated, and we'll step back and let them do their job.

Allocations in Rust: Dynamic memory

· 6 min read
Bradlee Speice

Managing dynamic memory is hard. Some languages assume users will do it themselves (C, C++), and some languages go to extreme lengths to protect users from themselves (Java, Python). In Rust, how the language uses dynamic memory (also referred to as the heap) is a system called ownership. And as the docs mention, ownership is Rust's most unique feature.

The heap is used in two situations; when the compiler is unable to predict either the total size of memory needed, or how long the memory is needed for, it allocates space in the heap.

Allocations in Rust: Fixed memory

· 16 min read
Bradlee Speice

const and static are perfectly fine, but it's relatively rare that we know at compile-time about either values or references that will be the same for the duration of our program. Put another way, it's not often the case that either you or your compiler knows how much memory your entire program will ever need.

However, there are still some optimizations the compiler can do if it knows how much memory individual functions will need. Specifically, the compiler can make use of "stack" memory (as opposed to "heap" memory) which can be managed far faster in both the short- and long-term.

Allocations in Rust: Global memory

· 8 min read
Bradlee Speice

The first memory type we'll look at is pretty special: when Rust can prove that a value is fixed for the life of a program (const), and when a reference is unique for the life of a program (static as a declaration, not 'static as a lifetime), we can make use of global memory. This special section of data is embedded directly in the program binary so that variables are ready to go once the program loads; no additional computation is necessary.

Understanding the value/reference distinction is important for reasons we'll go into below, and while the full specification for these two keywords is available, we'll take a hands-on approach to the topic.