Otávio C.

Twenty Years Later: Coltrane

For a year before starting my Computer Science master's, I worked as a researcher for a research group that was developing a parallel computing runtime called Anahy12. I wasn't there to work on the runtime itself. I came from a Physics degree, where I had spent time writing galaxy simulations and collision models in FORTRAN, on an old Dell running Linux. That background made me useful for something else: writing real simulations that could stress-test the library, publish papers around it, and generally explore what it could do beyond toy problems.

Anahy was built in C with a POSIX thread-like interface. You called athread_create to spawn work and athread_join to wait for it. Simple on the surface, but the model underneath was serious. The runtime built a Directed Acyclic Graph of tasks implicitly from the nesting of those calls, and scheduled that graph onto a fixed pool of Virtual Processors, each a real OS thread. Instead of blocking when joining a task that wasn't finished yet, an idle processor would help by running other pending work from the graph. The key idea, the one that stuck with me, was the separation between describing the concurrency of your application and letting the runtime handle the parallelism the hardware provides. You wrote the dependencies. The runtime figured out the rest.

I published a couple of papers as part of that work, and then the master's came. Among other things, I implemented a parallel version of Helbing's social force model, the model that reproduces the way crowds behave near exits, the clogging, the arches that form at doors, the counterintuitive result that people moving faster can slow the whole crowd down. The approach to parallelizing it turned out to be the same one I had used for galaxy simulations: Barnes-Hut, which decomposes the problem into a quadtree in 2D or an octree in 3D, making it possible to describe concurrent tasks that the runtime can then schedule for parallel execution. The math is different, but the structure of the problem is the same. That work was done using Anahy, and it remains one of the most interesting problems I ever implemented.

Then academia ended, and I moved on. First to embedded systems and virtualization, then to iOS development in San Francisco, and eventually Germany. Anahy stayed behind.

A few months ago, nostalgia got the better of me. I missed the old days of solving equations and implementing simulations, the kind of problems where the math and the code are inseparable. I wanted to revisit the galaxy collisions and the crowd simulations in Swift, to see those problems through a modern lens.

One thing led to another. Before writing a single simulation, I found myself thinking about the foundation. I could have reached for Swift's structured concurrency. It's first-party, well-designed, and what I use professionally. But the more I thought about it, the more I wanted to know whether the ideas behind Anahy, the same model, the same scheduling approach, could be re-implemented cleanly in Swift, without async/await, and still perform competitively on modern hardware. So that became the project.

The result is Coltrane. The API is higher level than Anahy's POSIX-like interface. You call spawn with a closure and get back a typed JobHandle<T>. You call join on the handle to get the result. The model underneath is the same: a shared task DAG built implicitly at runtime, a fixed pool of Virtual Processors, each a real OS thread sized to the core count by default, and work-helping as the scheduling strategy. When a VP joins a task that isn't finished yet, instead of blocking it helps by running other pending work from the graph.

The natural point of comparison isn't Anahy's C interface but Swift's own structured concurrency, which any Swift developer would reach for today. Side by side, the two approaches look similar on the surface:

// Coltrane
func fibonacciColtrane(_ n: Int) -> Int {
    guard n > 1 else { return n }
    let a = Coltrane.shared.spawn { fibonacciColtrane(n - 1) }
    let b = Coltrane.shared.spawn { fibonacciColtrane(n - 2) }
    return a.join() + b.join()
}

// Swift's async/await
func fibonacciAsync(_ n: Int) async -> Int {
    guard n > 1 else { return n }
    async let a = fibonacciAsync(n - 1)
    async let b = fibonacciAsync(n - 2)
    return await a + b
}

The structure is almost the same. What differs is everything underneath Coltrane. There is no async/await, no actors, no stack switching. Jobs run inline on a VP's real call stack, built on raw Thread, NSCondition, and NSRecursiveLock. That was a deliberate choice: I wanted to see how far the model could go on its own terms, without leaning on the concurrency machinery the language now provides.

It is a deliberate alternative, not a replacement. The point was never to stop using Swift's structured concurrency. The point was to understand whether the model still holds up, twenty years later, on hardware we never imagined.

It holds up.

Measured on an M4 Pro with 12 cores, Coltrane matches async/await on most workloads and beats it on some. The N-body force evaluation, the kind of problem I originally wanted to revisit, scales to roughly 7x on 8 Virtual Processors. N-Queens, Monte Carlo, and Black-Scholes reach around 9x by 12 VPs and match async/await closely. The recursive workloads, Fibonacci, merge sort, the ray tracer, land in a similar range.

The two exceptions are worth mentioning honestly. The bulk-synchronous stencils, Conway's Game of Life and the Gray-Scott reaction-diffusion simulation, peak at 8 VPs rather than 12. I think that's because the M4 Pro has 8 performance cores and 4 efficiency cores, and because every step's barrier waits on its slowest worker, spilling onto the slower efficiency cores at 10 to 12 VPs gates the whole step. Async/await handles this better.

The full benchmark table and all the demos are on GitHub, including the code, the numbers, and instructions to run everything yourself.

Which brings me back to where this started. Below are three renders of simulations using Coltrane. The first is a galaxy collision, the same class of problem I was simulating in FORTRAN on that old Dell more than twenty years ago.

Galaxy collision.

The other two are a crowd evacuating a room through a single 1.2-meter-wide door and through three 1.2-meter-wide doors. A simplified version of the Helbing model I implemented during my master's. The simulations show the arch forming at the door, and the clog does exactly what the model predicts.

Crowd evacuating a room through a single 1.2-meter-wide door.
Crowd evacuating a room through three 1.2-meter-wide doors.

And that's it. Now that the foundation is here, the simulations are just getting started.

  1. Exploiting multithreaded programming on cluster architectures Cordeiro, O. C., Peranconi, D. S., Real, L. C. V., Dall'Agnol, E. C., & Cavalheiro, G. G. (2005, May). Exploiting multithreaded programming on cluster architectures. In 19th International Symposium on High Performance Computing Systems and Applications (HPCS'05) (pp. 90-96). IEEE.

  2. Anahy: A Programming Environment for Cluster Computing Cavalheiro, G. G. H., Gaspary, L. P., Cardozo, M. A., & Cordeiro, O. C. (2006, June). Anahy: A programming environment for cluster computing. In International Conference on High Performance Computing for Computational Science (pp. 198-211). Berlin, Heidelberg: Springer Berlin Heidelberg.

#Handpick #Science #Software Engineering #Swift