Skip to content

Instantly share code, notes, and snippets.

@radfast
Created February 12, 2016 21:02
Show Gist options
  • Save radfast/6f966fc0577b3e1a980d to your computer and use it in GitHub Desktop.
Save radfast/6f966fc0577b3e1a980d to your computer and use it in GitHub Desktop.
Memory Use and Performance in Minecraft 1.8
[From an original post by sp614x here: http://www.minecraftforum.net/forums/mapping-and-modding/minecraft-mods/1272953-optifine-hd-h2-fps-boost-hd-textures-shaders-and?comment=43757]
Minecraft 1.8 has so many performance problems that I just don't know where to start with.
Maybe the biggest and the ugliest problem is the memory allocation. Currently the game allocates (and throws away immediately) 50 MB/sec when standing still and up to 200 MB/sec when moving. That is just crazy.
What happens when the game allocates 200 MB memory every second and discards them immediately?
1. With a default memory limit of 1GB (1000 MB) and working memory of about 200 MB Java has to make a full garbage collection every 4 seconds otherwise it would run out of memory. When running with 60 fps, one frame takes about 16 ms. In order not to be noticeable, the garbage collection should run in 10-15 ms maximum. In this minimal time it has to decide which of the several hundred thausand newly generated objects are garbage and can be discarded and which are not. This is a huge amount of work and it needs a very powerful CPU in order to finish in 10 ms.
2. Why not give it more memory?
Let's give Minecraft 4 GB of RAM to play with. This would need a PC with at least 8 GB RAM (as the real memory usage is almost double the memory visible in Java). If the VM decides to use all the memory, then it will increase the time between the garbage collections (20 sec instead of 4), but it will also increase the garbage collection time by 4, so every 20 seconds there will be one massive lag spike.
3. Why not use incremental garbage collection?
The latest version of the launcher by default enables incremental garbage collection (-XX:+CMSIncrementalMode) which in theory should replace the one big GC with many shorter incremental GCs. However the problem is that the time at which the smaller GCs happen and their duration are mostly random. Also they are not much shorter (maybe 50%) than a full scale GC. That means that the FPS starts to fluctuate up and down and there are a lot of random lag spikes. The stable FPS with a lag spike from time to time is replaced with unstable FPS and microstutter (or not very micro depending on the CPU). This strategy can only work with a powerful enough CPU so that the random lag spikes become small enough not to be noticeable.
4. How did that work in previous releases?
The previous Minecraft releases were much less memory hungry. The original Notch code (pre 1.3) was allocating about 10-20 MB/sec which was much more easy to control and optimize. The rendering itself needed only 1-2 MB/sec and was designed to minimize memory waste (reusing buffers, etc). The 200 MB/sec is pushing the limits and forcing the garbage collector to do a lot of work which takes time. If it was possible to control how and when the GC works then maybe it would be possible to distribute the GC pauses such that they are not noticeable or less disturbing. However there is no such control in the current Java VM.
5. Why is 1.8 allocating so much memory?
This is the best part - over 90% of the memory allocation is not needed at all. Most of the memory is probably allocated to make the life of the developers easier.
- There are huge amounts of objects which are allocated and discarded milliseconds later.
- All internal methods which used parameters (x, y, z) are now converted to one parameter (BlockPos) which is immutable. So if you need to check another position around the current one you have to allocate a new BlockPos or invent some object cache which will probaby be slower. This alone is a huge memory waste.
- The chunk loading is allocating a lot of memory just to pass vertex data around. The excuse is probably "mutithreading", however this is not necessary at all (see the last OptiFine for 1.7).
- the list goes on and on ...
The general trend is that the developers do not care that much about memory allocation and use "best industry practices" without understanding the consequences. The standard reasoning being "immutables are good", "allocating new memory is faster than caching", "the garbage collector is so good these days" and so on.
Allocating new memory is really faster than caching (Java is even faster than C++ when it comes to dynamic memory), but getting rid of the allocated memory is not faster and it is not predictable at all. Minecraft is a "real-time" application and to get a stable framerate it needs either minimal runtime memory allocation (pre 1.3) or controllable garbage collecting, which is just not possible with the current Java VM.
6. What can be done to fix it?
If there are 2 or 3 places which are wasting memory (bugs), then OptiFine can fix them individually. Otherwise a bigger refactoring of the Minecraft internals will be needed, which is a huge task and not possible for OptiFine.
7. Example
A sample log of GC activity with effective FPS for the GC lag spikes is available here.
- the average rendering FPS is about 50 FPS
- the GC lag spikes have effective FPS of 7-20
- there are 1-2 lag spikes per second caused by GC activity
tldr; When 1.8 is lagging and stuttering the garbage collector is working like crazy and is doing work which has nothing to do with the game itself (rendering, running the internal server, loading chunks, etc). Instead it is constantly cleaning the mess behind the code which thinks that memory allocation is "cheap".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment