DEV Community

Cover image for Beating Cache Misses: Transforming POJOs into Cache-Friendly SoA-Style classes
Mario Mignemi
Mario Mignemi

Posted on

Beating Cache Misses: Transforming POJOs into Cache-Friendly SoA-Style classes

The code I was writing in Raylib felt familiar. It wasn't the clean, modern Java code I expected but the exact same tangled spaghetti I used to produce with Dark Basic Classic back in high school.

Back then, I didn't have an internet connection to look up tutorials. I only had a Dark Basic CD my father bought me, and my attempts at game programming were a disaster. Every project ended up with a mess of global state and GOTO statements. At the time, I couldn't understand why my game logic would collapse under its own weight, but looking back, the culprit was the language itself. Dark Basic’s loosely structured BASIC dialect made it far too easy to write code that was impossible to maintain.


Why Thousands of Birds Kill Performance

As I started building a flocking simulation in Raylib, I knew I was heading toward a performance wall. If I wanted to simulate thousands of birds moving independently, the standard Object-Oriented (OO) approach would fail.

In a typical OO approach, you would create a Bird object for every entity. When you iterate through a List<Bird> to update their positions, the CPU has to jump around different locations in memory to find each object. This causes cache misses. The CPU spends more time waiting for data to arrive from RAM than it does actually performing the math. My simulation stuttered even though CPU usage stayed at 30%, a sign that the bottleneck was memory access, not computation.

I wanted to learn Data-Oriented Design (DOD) to solve this, and the mental shift was brutal. DOD requires you to stop thinking about objects and start thinking in terms of arrays of primitives. It feels counter-intuitive to a developer trained in years of OOP.


A Lombok for Data-Oriented Design

If Lombok can generate boilerplate source code at compile time to save us from the "ceremony" of Java, why can't I do the same for performance?

This idea led me to create Flock, a Java library that uses an annotation processor to transform standard object-oriented classes into high-performance, memory-efficient companion classes.

Here is how the transformation works. You write a simple, readable POJO (Plain Old Java Object) and annotate it with @DataOriented:

Example of POJO

The Flock annotation processor then generates a companion class during compilation. Instead of an List of objects, it creates a "Struct" of Arrays (SoA):

Example of SoA-style class

Why does this matter?
By using primitive arrays, the data is stored contiguously in memory. When the CPU fetches the x coordinate for bird #1, it also pre-fetches the x coordinates for birds #2, #3, and #4 into the cache. This massive increase in data locality is what allows you to scale from hundreds of birds to thousands without a frame-rate stutters.


Two Birds with One Stone (no pun intended)

With Flock, you get the best of both worlds: you can use intuitive, object-oriented class for your high-level game state, but use the generated Flocks for your heavy-duty.

The project taught me two things I’ve wanted to learn for months: the Raylib library and the principles of Data-Oriented Design. It also served as a reminder that even as a senior developer, we are always one side project away from a moment of déjà vu.

While Java's Streams are sufficient for most business logic, they aren't enough when performance is a hard requirement. Flock is open source and available on Codeberg if you want to explore the code base or contribute.


We've all had a side project that sent us back to old habits. What's yours? Tell me in the comments!

My Game's Links:
The Weight of One - Official Channel
The Weight of One - Itch.io Page

My Socials:
My Linkedin
My Personal Channel

You can find my Dev Vlogs in both Youtube channels!

Top comments (0)