I recently took over the optimization of a 2D project that has been in development for over five years. The project relies heavily on Spine, which resulted in extremely high GC (Garbage Collection) and a massive build size.
Constraint: Since the project is over five years old, many original Spine source files (.spine) have been lost, limiting our ability to simply “re-export” everything from scratch.
Step 1: Converting JSON to Binary (.skel)
Because many source files were missing, I wrote a tool to read the existing JSON data and save it back out as binary (.skel).
- Result: This provided a massive boost. Loading speeds improved several-fold, and memory GC dropped by an order of magnitude.
Step 2: Texture Compression & Non-Power-of-Two (NPOT)
For assets where source files were still available, I had the technical artists re-export them using non-square and non-power-of-two textures, utilizing the ASTC format.
- Result: Further reduced the memory footprint and improved GPU efficiency.
Step 3: Stream Reading Optimization
I modified the .skel loading logic, replacing standard Stream reading with Direct Pointer Manipulation (Pointer Offsets).
- Result: This doubled the loading speed once again compared to the standard binary reader.
Step 4: Stripping and Compressing Binary Data
Despite the switch to binary, the files remained too large (several exceeding 10MB). I implemented a custom export/processing pass to:
- Remove non-essential data.
- String Pooling: Move all strings to a header and use integer indices to reference them.
- Timeline Merging: Optimize timelines by omitting redundant keyframe data when intermediate values are identical.
- Result: Reduced
.skelfile sizes by 50%–70%. - Reference: Spine Binary Format Official Documentation
Step 5: Lazy Loading for Animations
While we successfully optimized loading from several seconds (JSON) to several hundred milliseconds (Binary), it still wasn’t fast enough. To reach the goal of tens of milliseconds, I analyzed the performance bottlenecks.
- Analysis: Over 80% of the loading overhead originated from parsing
animations. - Solution: I implemented Lazy Parsing. The system now skips animation parsing during the initial load and only parses a specific animation the first time it is actually used. Since most animations are not played simultaneously—and some are rarely used at all—this drastically cuts the initial cost.
Open Source Optimization
I have shared the optimized source code on GitHub: spine-optimize