Building a Copying Garbage Collector for Plush: Taming Memory in an Actor-Based Language
Share this article
Building a Copying Garbage Collector for Plush: Taming Memory in an Actor-Based Language
In the landscape of programming language development, few tasks are as simultaneously essential and daunting as implementing a garbage collector. For the creators of Plush, a dynamically-typed language inspired by Lox and JavaScript, this challenge was particularly complex due to its actor-based parallelism model. After months of procrastination, the team has now successfully implemented a copying garbage collector, unlocking the language's potential for long-running applications and complex computations.
The Actor Model's Memory Management Dilemma
Plush was designed from the ground up to simplify parallel programming through an actor-based model. In this paradigm, independent actors communicate exclusively by sending messages to one another, eliminating the need for locks and complex synchronization mechanisms. This design makes multithreaded programming accessible even to beginners, but it introduces significant challenges for memory management.
"When you send a message from one actor to another, you're essentially passing a potentially complex subgraph of heap-allocated objects," explains the language's creator. "These objects might contain cycles, they might be large, and they need to be managed efficiently across different actors' memory spaces."
The core dilemma facing the Plush team was how to handle message passing between actors without forcing programmers to manually freeze objects or perform deep copies before sending messages. Requiring immutability would add significant complexity to the language and require a VM-wide garbage collection mechanism to synchronize across all actors.
The Double Allocator Innovation
After careful consideration, the team implemented an innovative double allocator design that allows each actor to maintain two separate memory spaces:
- A private allocator for internal allocations that requires no locking
- A mailbox allocator specifically for receiving messages from other actors
This design elegantly solves the synchronization problem while maintaining high performance. "The key benefit is that each actor can do its own private allocations without locking, which maximizes allocation performance," the creator notes. "We decided to go with a bump allocator and a copying GC design to keep things simple and ensure that allocating memory is very fast."
The bump allocator, a simple allocation strategy that moves a "bump pointer" forward as memory is allocated, provides excellent performance characteristics for the common case where objects have similar lifespans. Combined with a copying garbage collector, this creates an efficient memory management system that works well with Plush's actor model.
From Procrastination to Implementation
Despite the elegance of the design, the team initially delayed implementing the garbage collector. "I kept putting it off because I was dreading it," admits the creator. "Past experience taught me that implementing a GC can be very error-prone, with lots of sneaky bugs that can force you to refactor key parts of your VM."
The turning point came when a friend, Marco, offered to collaborate on the implementation. "Having someone to work with on the feature made it much easier to find the motivation to tackle this challenge," the creator explains.
Working together, the team completed approximately 90% of the garbage collector implementation in just over two weeks. They employed a systematic testing approach, writing adversarial test programs designed to stress the GC and surface potential bugs. "That strategy proved effective," the creator notes. "It turns out it's sometimes not that stressful to do hard things if you break them up into many small steps."
Real-World Validation with the Boing Ball Demo
With the garbage collector in place, the team created a tribute to the classic Amiga Boing Ball demo—a 3D animation that runs seamlessly at 60 frames per second on a MacBook Air M1. "The program performs GC about once per second, and the pause isn't noticeable," the creator reports. "Even though it's only flat-shaded polygons, I feel pretty proud that the Plush interpreter is fast enough to do 3D graphics in real-time."
This demonstration serves as both a technical achievement and a validation of the language's design principles. The ability to handle complex 3D graphics with minimal garbage collection pauses indicates that the implementation successfully balances performance with memory safety.
Unresolved Challenges and Community Collaboration
While the core garbage collector functionality is now in place, several challenges remain:
Message Queue Management: If an actor receives many large objects and its message queue becomes backlogged, the message allocator can fill up, causing the program to panic. The solution will require implementing logic for actors to wait and retry when the receiver's allocator is full.
Dynamic Allocation Growth: The message allocator needs the ability to grow in size when necessary to handle bursts of incoming messages.
Performance Optimization: Collections can become slow once the system manages more than 300K objects. The team suspects this may be related to Rust hashmap performance or potential quadratic behavior in some algorithms.
"We're looking for contributors to help profile and investigate that performance issue," the creator invites. "We're also hoping to see more demo programs that showcase what's possible with Plush now that we have both graphics and audio output capabilities."
The language's unique combination of actor-based parallelism, simple syntax for graphics and sound programming, and now robust memory management opens up exciting possibilities for creative coding, procedural generation, and real-time multimedia applications.
The Future of Parallel Language Design
The Plush project offers valuable insights into the challenges and opportunities of modern programming language design. By prioritizing developer experience—simplifying parallel programming while maintaining performance—the language demonstrates that it's possible to have both safety and expressiveness.
As computing continues to evolve toward increasingly parallel architectures, languages like Plush that provide high-level abstractions for concurrency without sacrificing performance will become increasingly valuable. The successful implementation of a garbage collector that works seamlessly with an actor model represents an important step forward in this direction.
For developers interested in language design, memory management, or parallel programming, the Plush project offers both a practical implementation and a case study in overcoming complex technical challenges through systematic design and collaborative development.