Skip to content

Rebuild cube io cache - #154

Open
Jakfut wants to merge 3 commits into
GTNewHorizons:masterfrom
JakfutCC:rebuild-cube-io-cache
Open

Jakfut wants to merge 3 commits into
GTNewHorizons:masterfrom
JakfutCC:rebuild-cube-io-cache

Conversation

@Jakfut

@Jakfut Jakfut commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

The current CubeIO's cube and column caches keep the NBT of every saved and preloaded cube as objects: up to 30,000 entries, and up to 60,000 while entries are younger than 120s.

In a test run on a nearly empty world,one player flying 1,500 blocks and back causes the current cube cache to reach 60,086 entries. Heap after GC stayed 300–500 MiB higher than with this PR.

This PR replaces both caches with one IOCache per kind:

  • Pending saves: kept until written, never evicted. Loads get a copy, as before.
  • Read-ahead data: handed to the load without a copy and dropped from the cache. Data that is never loaded expires after 30 s.
  • Unloaded cubes and columns: after the write, their data is kept LZ4-compressed (0.75 KiB per cube) within a byte budget. The oldest entries are evicted first, and an entry is dropped once its cube loads again. Saves of cubes that stay loaded (autosave) are not kept.
  • Absent markers: kept for positions known not to exist on disk, counted in the budget.
  • Stale reads: a read that started before a later save of the same position is refused (or re-read), even after that save's entry is gone. This keeps the protection from Prevent stale preload results from replacing saves #86.
  • flush()/close(): wait until every save has been written, also after write failures.

New config option: optimizations.saveCacheMegabytes. The default is 32 MiB per cubic dimension for cubes; columns get an eighth on top. 0 keeps only the pending saves. Using LZ4 compression, 32MiB can hold about 40.000 cubes

The first commit makes each dimension write its saves one batch at a time, in the order they were made. On master, two background threads could write batches of the same dimension at once, so an older save of a cube could reach the disk after a newer one and overwrite it. The old cache kept returning the newer data for up to 120 s, which hid this until a restart. Reads, world generation and other dimensions still run in parallel.

This is also preparation for 2x2x2 cube bundles in #139: pending saves are tracked per position and written in order, so a later change can hold them briefly and write whole bundles.

@Nullable
static byte[] pack(NBTTagCompound tag) {
try {
ByteArrayOutputStream raw = new ByteArrayOutputStream(4096);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use CCNBTUtils.getTagSizeEstimate you can avoid over-allocating the buffer for small tags. 4096 is basically nothing, but the jvm has to do a memset 0 on arrays so their allocation cost increases with size. getTagSizeEstimate avoids buffer reallocations too.

Eventually I'd like to use netty's pooling in a lot of places, or have a byte[] / int[] / long[] cache, because these arrays are 99% of CC's allocations.

@RecursivePineapple RecursivePineapple added the Merge Into Prerelease PR will be added to -pre releases (via the dev branch). label Oct 31, 2026
@Jakfut

Jakfut commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

changed it to CCNBTUtils.getTagSizeEstimate like you suggested

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Merge Into Prerelease PR will be added to -pre releases (via the dev branch).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants