Sodium

Sodium

35M Downloads

Heavy stuttering related to block updates + Buffer Crash

ruvaldak opened this issue ยท 2 comments

commented

Version Information

All versions beginning with 0.2.0 release and leading up to the latest 1.16.5 version of the mod as build from source. Also present on the Iris fork, as well as the Iris Starline fork of Sodium.

Expected Behavior

When around a large amount of block updates, such as growing sugarcane, to remain smooth without stuttering.

Actual Behavior

As of right now, after some point of being around sugarcane, or any other source of a very large amount of block updates, the game begins to stutter very badly. After some time, or in some specific circumstances, the game will crash.

Reproduction Steps

New World (1).zip

Load up this world, press the button on the command block to set random tick speed to 65 (or 75, not sure) to allow the sugarcane to grow, and prepare for stuttering. This behavior is not present in Optifine.

System Information

CPU: i7-9700k
RAM: 16GB DDR4 @ 4000MHz CL15
GPU: EVGA Black 2080Ti
PSU: 800W 80+ Platinum

Other Information

More information: IrisShaders/Iris#465
This issue is present when just using the 1.16.5/next branch of Sodium, however, it becomes worse when also using Iris and their Sodium fork. I have not tested on 1.17.

commented

After using a few debug statements, I found that the problem may be caused by memory fragmentation in memory managed by class GlBufferArena.

My diff for the relevant debug output was:

diff --git a/src/main/java/me/jellysquid/mods/sodium/client/gl/arena/GlBufferArena.java b/src/main/java/me/jellysquid/mods/sodium/client/gl/arena/GlBufferArena.java
index 3b4a7b7..1920077 100644
--- a/src/main/java/me/jellysquid/mods/sodium/client/gl/arena/GlBufferArena.java
+++ b/src/main/java/me/jellysquid/mods/sodium/client/gl/arena/GlBufferArena.java
@@ -1,6 +1,7 @@
 package me.jellysquid.mods.sodium.client.gl.arena;
 
 import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
+import me.jellysquid.mods.sodium.client.SodiumClientMod;
 import me.jellysquid.mods.sodium.client.gl.buffer.GlBuffer;
 import me.jellysquid.mods.sodium.client.gl.buffer.GlBufferTarget;
 import me.jellysquid.mods.sodium.client.gl.buffer.GlBufferUsage;
@@ -23,6 +24,7 @@ public class GlBufferArena {
     private int position;
     private int capacity;
     private int allocCount;
+    private int allocBytes;
 
     public GlBufferArena(RenderDevice device, int initialSize, int resizeIncrement) {
         this.device = device;
@@ -46,6 +48,8 @@ public class GlBufferArena {
 
         this.vertexBuffer = dst;
         this.capacity = newCapacity;
+        SodiumClientMod.logger().info("GlBufferArena resized to {}, allocCount={}, allocBytes={}", newCapacity,
+               allocCount, allocBytes);
     }
 
     public void prepareBuffer(CommandList commandList, int bytes) {
@@ -74,6 +78,7 @@ public class GlBufferArena {
         }
 
         this.allocCount--;
+        this.allocBytes -= segment.getLength();
     }
 
     private GlBufferSegment alloc(int len) {
@@ -86,6 +91,7 @@ public class GlBufferArena {
         }
 
         this.allocCount++;
+        this.allocBytes += segment.getLength();
 
         return segment;
     }

Example output:

00:08:51.435 Sodium main GlBufferArena resized to 187607408, allocCount=315, allocBytes=10920640
00:08:51.437 Sodium main GlBufferArena resized to 38797312, allocCount=163, allocBytes=6350400
00:09:10.485 Sodium main GlBufferArena resized to 189415808, allocCount=315, allocBytes=10923040
00:09:22.535 Sodium main GlBufferArena resized to 191224528, allocCount=315, allocBytes=10923840

My hypothesis:
The problem is really common when continuous memory is naively handed out as non-fixed size chunks, as it is here. Bigger than required chunks of memory are being partitioned into 2 pieces, with the first piece returned and the second piece added to the free list. They are never combined resulting in an increasing number of GlBufferSegments with ever-decreasing length, effectively making the already allocated memory unusable. These small free blocks are never used again since the allocation sizes tend to slowly increase (for a given chunk, when there is something growing in it, like bamboo).

Since GlBufferArena had a lot of work done in 0.17.x already, which seems to be really complex and (maybe) addresses the underlying issue, I don't know how I could help fix this in 0.16.x, and by extension, Iris shaders.

commented

I have made a workaround: I've rewritten the allocator to combine freed segments. Using Guava's TreeRangeSet. This resulted in much less memory made unusable due to fragmentation, therefore much less memory allocated, therefore less stutter (practically non-noticeable, after the buffer size settles).

Logs are as follows:

01:15:25.188 GlBufferArena resized to 10485760, allocCount=381, allocBytes=8013920
01:15:25.262 GlBufferArena resized to 14680064, allocCount=193, allocBytes=9868720
01:15:29.262 GlBufferArena resized to 2097152, allocCount=26, allocBytes=1007200
01:15:45.246 GlBufferArena resized to 9437184, allocCount=126, allocBytes=3205680
01:16:14.387 GlBufferArena resized to 7340032, allocCount=52, allocBytes=3318000
01:16:27.025 GlBufferArena resized to 5242880, allocCount=14, allocBytes=965920
01:17:08.984 GlBufferArena resized to 16466384, allocCount=436, allocBytes=11833600
01:17:31.682 GlBufferArena resized to 12644208, allocCount=141, allocBytes=3965520
01:19:04.002 GlBufferArena resized to 3145728, allocCount=114, allocBytes=2012080

If you are interested, I can make a pull request later.