PaperMC/CraftBukkit-Patches/0006-Implement-lightening-of-NibbleArrays-only-allocate-b.patch
2013-01-19 19:22:25 +11:00

389 lines
16 KiB
Diff

From 312e0e8499c59630a87f6784285ce07f80156df9 Mon Sep 17 00:00:00 2001
From: Mike Primm <mike@primmhome.com>
Date: Sun, 23 Dec 2012 14:46:23 -0600
Subject: [PATCH] Implement 'lightening' of NibbleArrays - only allocate
buffers when non-trivial value Saving from 40-45% of memory use by chunk
section data.
Finish up NibbleArray lightening work - use for Snapshots, reduce copies
Fix nibble handling with NBT - arrays aren't copied by NBTByteArray
---
.../net/minecraft/server/ChunkRegionLoader.java | 10 +-
.../java/net/minecraft/server/ChunkSection.java | 24 ++--
.../java/net/minecraft/server/NibbleArray.java | 122 ++++++++++++++++++++-
.../net/minecraft/server/Packet51MapChunk.java | 28 +++--
.../java/org/bukkit/craftbukkit/CraftChunk.java | 44 +++++++-
5 files changed, 197 insertions(+), 31 deletions(-)
diff --git a/src/main/java/net/minecraft/server/ChunkRegionLoader.java b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
index e5e60a9..2e72ab5 100644
--- a/src/main/java/net/minecraft/server/ChunkRegionLoader.java
+++ b/src/main/java/net/minecraft/server/ChunkRegionLoader.java
@@ -225,15 +225,15 @@ public class ChunkRegionLoader implements IAsyncChunkSaver, IChunkLoader {
nbttagcompound1.setByte("Y", (byte) (chunksection.d() >> 4 & 255));
nbttagcompound1.setByteArray("Blocks", chunksection.g());
if (chunksection.i() != null) {
- nbttagcompound1.setByteArray("Add", chunksection.i().a);
+ nbttagcompound1.setByteArray("Add", chunksection.i().getValueArray()); // Spigot
}
- nbttagcompound1.setByteArray("Data", chunksection.j().a);
- nbttagcompound1.setByteArray("BlockLight", chunksection.k().a);
+ nbttagcompound1.setByteArray("Data", chunksection.j().getValueArray()); // Spigot
+ nbttagcompound1.setByteArray("BlockLight", chunksection.k().getValueArray()); // Spigot
if (flag) {
- nbttagcompound1.setByteArray("SkyLight", chunksection.l().a);
+ nbttagcompound1.setByteArray("SkyLight", chunksection.l().getValueArray()); // Spigot
} else {
- nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.k().a.length]);
+ nbttagcompound1.setByteArray("SkyLight", new byte[chunksection.k().getValueArray().length]); // Spigot
}
nbttaglist.add(nbttagcompound1);
diff --git a/src/main/java/net/minecraft/server/ChunkSection.java b/src/main/java/net/minecraft/server/ChunkSection.java
index 051cf6d..42e669c 100644
--- a/src/main/java/net/minecraft/server/ChunkSection.java
+++ b/src/main/java/net/minecraft/server/ChunkSection.java
@@ -134,7 +134,8 @@ public class ChunkSection {
}
}
} else {
- byte[] ext = this.extBlockIds.a;
+ this.extBlockIds.forceToNonTrivialArray(); // Spigot
+ byte[] ext = this.extBlockIds.getValueArray();
for (int off = 0, off2 = 0; off < blkIds.length;) {
byte extid = ext[off2];
int l = (blkIds[off] & 0xFF) | ((extid & 0xF) << 8); // Even data
@@ -165,6 +166,12 @@ public class ChunkSection {
off++;
off2++;
}
+ // Spigot start
+ this.extBlockIds.detectAndProcessTrivialArray();
+ if (this.extBlockIds.isTrivialArray() && (this.extBlockIds.getTrivialArrayValue() == 0)) {
+ this.extBlockIds = null;
+ }
+ // Spigot end
}
this.nonEmptyBlockCount = cntNonEmpty;
this.tickingBlockCount = cntTicking;
@@ -225,12 +232,11 @@ public class ChunkSection {
public void a(NibbleArray nibblearray) {
// CraftBukkit start - don't hang on to an empty nibble array
boolean empty = true;
- for (int i = 0; i < nibblearray.a.length; i++) {
- if (nibblearray.a[i] != 0) {
- empty = false;
- break;
- }
+ // Spigot start
+ if ((!nibblearray.isTrivialArray()) || (nibblearray.getTrivialArrayValue() != 0)) {
+ empty = false;
}
+ // Spigot end
if (empty) {
return;
@@ -253,10 +259,8 @@ public class ChunkSection {
// Spigot start - validate/correct nibble array
private static final NibbleArray validateNibbleArray(NibbleArray na) {
- if ((na != null) && (na.a.length < 2048)) {
- NibbleArray newna = new NibbleArray(4096, 4);
- System.arraycopy(na.a, 0, newna.a, 0, na.a.length);
- na = newna;
+ if ((na != null) && (na.getByteLength() < 2048)) {
+ na.resizeArray(2048);
}
return na;
}
diff --git a/src/main/java/net/minecraft/server/NibbleArray.java b/src/main/java/net/minecraft/server/NibbleArray.java
index 5d75a54..c9bc20c 100644
--- a/src/main/java/net/minecraft/server/NibbleArray.java
+++ b/src/main/java/net/minecraft/server/NibbleArray.java
@@ -1,13 +1,117 @@
package net.minecraft.server;
+import java.util.Arrays; // Spigot
+
public class NibbleArray {
- public final byte[] a;
+ private byte[] a; // Spigot - remove final, make private (anyone directly accessing this is broken already)
private final int b;
private final int c;
+ // Spigot start
+ private byte trivialValue;
+ private byte trivialByte;
+ private int length;
+ private static final int LEN2K = 2048; // Universal length used right now - optimize around this
+ private static final byte[][] TrivLen2k;
+
+ static {
+ TrivLen2k = new byte[16][];
+ for (int i = 0; i < 16; i++) {
+ TrivLen2k[i] = new byte[LEN2K];
+ Arrays.fill(TrivLen2k[i], (byte) (i | (i << 4)));
+ }
+ }
+
+ // Try to convert array to trivial array
+ public void detectAndProcessTrivialArray() {
+ trivialValue = (byte) (a[0] & 0xF);
+ trivialByte = (byte) (trivialValue | (trivialValue << 4));
+ for (int i = 0; i < a.length; i++) {
+ if (a[i] != trivialByte) return;
+ }
+ // All values matches, so array is trivial
+ this.length = a.length;
+ this.a = null;
+ }
+
+ // Force array to non-trivial state
+ public void forceToNonTrivialArray() {
+ if (this.a == null) {
+ this.a = new byte[this.length];
+ if (this.trivialByte != 0) {
+ Arrays.fill(this.a, this.trivialByte);
+ }
+ }
+ }
+
+ // Test if array is in trivial state
+ public boolean isTrivialArray() {
+ return (this.a == null);
+ }
+
+ // Get value of all elements (only valid if array is in trivial state)
+ public int getTrivialArrayValue() {
+ return this.trivialValue;
+ }
+
+ // Get logical length of byte array for nibble data (whether trivial or non-trivial)
+ public int getByteLength() {
+ if (this.a == null) {
+ return this.length;
+ } else {
+ return this.a.length;
+ }
+ }
+
+ // Return byte encoding of array (whether trivial or non-trivial) - returns read-only array if trivial (do not modify!)
+ public byte[] getValueArray() {
+ if (this.a != null) {
+ return this.a;
+ } else {
+ byte[] rslt;
+
+ if (this.length == LEN2K) { // All current uses are 2k long, but be safe
+ rslt = TrivLen2k[this.trivialValue];
+ } else {
+ rslt = new byte[this.length];
+ if (this.trivialByte != 0) {
+ Arrays.fill(rslt, this.trivialByte);
+ }
+ }
+ return rslt;
+ }
+ }
+
+ // Copy byte representation of array to given offset in given byte array
+ public int copyToByteArray(byte[] dest, int off) {
+ if (this.a == null) {
+ Arrays.fill(dest, off, off + this.length, this.trivialByte);
+ return off + this.length;
+ } else {
+ System.arraycopy(this.a, 0, dest, off, this.a.length);
+ return off + this.a.length;
+ }
+ }
+
+ // Resize array to given byte length
+ public void resizeArray(int len) {
+ if (this.a == null) {
+ this.length = len;
+ } else if (this.a.length != len) {
+ byte[] newa = new byte[len];
+ System.arraycopy(this.a, 0, newa, 0, ((this.a.length > len) ? len : this.a.length));
+ this.a = newa;
+ }
+ }
+ // Spigot end
public NibbleArray(int i, int j) {
- this.a = new byte[i >> 1];
+ // Spigot start
+ //this.a = new byte[i >> 1];
+ this.a = null; // Start off as trivial value (all same zero value)
+ this.length = i >> 1;
+ this.trivialByte = this.trivialValue = 0;
+ // Spigot end
this.b = j;
this.c = j + 4;
}
@@ -16,9 +120,11 @@ public class NibbleArray {
this.a = abyte;
this.b = i;
this.c = i + 4;
+ detectAndProcessTrivialArray(); // Spigot
}
public int a(int i, int j, int k) {
+ if (this.a == null) return this.trivialValue; // Spigot
int l = j << this.c | k << this.b | i;
int i1 = l >> 1;
int j1 = l & 1;
@@ -27,6 +133,18 @@ public class NibbleArray {
}
public void a(int i, int j, int k, int l) {
+ // Spigot start
+ if (this.a == null) {
+ if (l != this.trivialValue) { // Not same as trivial value, array no longer trivial
+ this.a = new byte[this.length];
+ if (this.trivialByte != 0) {
+ Arrays.fill(this.a, this.trivialByte);
+ }
+ } else {
+ return;
+ }
+ }
+ // Spigot end
int i1 = j << this.c | k << this.b | i;
int j1 = i1 >> 1;
int k1 = i1 & 1;
diff --git a/src/main/java/net/minecraft/server/Packet51MapChunk.java b/src/main/java/net/minecraft/server/Packet51MapChunk.java
index 14a6245..ee179be 100644
--- a/src/main/java/net/minecraft/server/Packet51MapChunk.java
+++ b/src/main/java/net/minecraft/server/Packet51MapChunk.java
@@ -139,16 +139,22 @@ public class Packet51MapChunk extends Packet {
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].a()) && (i & 1 << l) != 0) {
nibblearray = achunksection[l].j();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ // Spigot start
+ // System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
+ // j += nibblearray.a.length;
+ j = nibblearray.copyToByteArray(abyte, j);
+ // Spigot end
}
}
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].a()) && (i & 1 << l) != 0) {
nibblearray = achunksection[l].k();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ // Spigot start
+ // System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
+ // j += nibblearray.a.length;
+ j = nibblearray.copyToByteArray(abyte, j);
+ // Spigot end
}
}
@@ -156,8 +162,11 @@ public class Packet51MapChunk extends Packet {
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].a()) && (i & 1 << l) != 0) {
nibblearray = achunksection[l].l();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ // Spigot start
+ // System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
+ // j += nibblearray.a.length;
+ j = nibblearray.copyToByteArray(abyte, j);
+ // Spigot end
}
}
}
@@ -166,8 +175,11 @@ public class Packet51MapChunk extends Packet {
for (l = 0; l < achunksection.length; ++l) {
if (achunksection[l] != null && (!flag || !achunksection[l].a()) && achunksection[l].i() != null && (i & 1 << l) != 0) {
nibblearray = achunksection[l].i();
- System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
- j += nibblearray.a.length;
+ // Spigot start
+ //System.arraycopy(nibblearray.a, 0, abyte, j, nibblearray.a.length);
+ //j += nibblearray.a.length;
+ j = nibblearray.copyToByteArray(abyte, j);
+ // Spigot end
}
}
}
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
index c3b9113..e34e781 100644
--- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java
@@ -174,7 +174,18 @@ public class CraftChunk implements Chunk {
}
if (cs[i].i() != null) { /* If we've got extended IDs */
- byte[] extids = cs[i].i().a;
+ // Spigot start
+ if (cs[i].i().isTrivialArray()) {
+ int tval = cs[i].i().getTrivialArrayValue();
+ if (tval != 0) {
+ tval = tval << 8;
+ for (int j = 0; j < 4096; j++) {
+ blockids[j << 1] |= tval;
+ }
+ }
+ } else {
+ byte[] extids = cs[i].i().getValueArray();
+ // Spigot end
for (int j = 0; j < 2048; j++) {
short b = (short) (extids[j] & 0xFF);
@@ -186,21 +197,42 @@ public class CraftChunk implements Chunk {
blockids[j<<1] |= (b & 0x0F) << 8;
blockids[(j<<1)+1] |= (b & 0xF0) << 4;
}
+ } // Spigot
}
sectionBlockIDs[i] = blockids;
/* Get block data nibbles */
- sectionBlockData[i] = new byte[2048];
- System.arraycopy(cs[i].j().a, 0, sectionBlockData[i], 0, 2048); // Should be getData
+ // Spigot start
+ if (cs[i].j().isTrivialArray() && (cs[i].j().getTrivialArrayValue() == 0)) {
+ sectionBlockData[i] = emptyData;
+ } else {
+ sectionBlockData[i] = new byte[2048];
+ cs[i].j().copyToByteArray(sectionBlockData[i], 0);
+ }
if (cs[i].l() == null) {
sectionSkyLights[i] = emptyData;
+ }
+ else if (cs[i].l().isTrivialArray()) {
+ if (cs[i].l().getTrivialArrayValue() == 0) {
+ sectionSkyLights[i] = emptyData;
+ } else if (cs[i].l().getTrivialArrayValue() == 15) {
+ sectionSkyLights[i] = emptySkyLight;
+ } else {
+ sectionSkyLights[i] = new byte[2048];
+ cs[i].l().copyToByteArray(sectionSkyLights[i], 0);
+ }
} else {
sectionSkyLights[i] = new byte[2048];
- System.arraycopy(cs[i].l().a, 0, sectionSkyLights[i], 0, 2048); // Should be getSkyLight
+ cs[i].l().copyToByteArray(sectionSkyLights[i], 0);
+ }
+ if (cs[i].k().isTrivialArray() && (cs[i].k().getTrivialArrayValue() == 0)) {
+ sectionEmitLights[i] = emptyData;
+ } else {
+ sectionEmitLights[i] = new byte[2048];
+ cs[i].k().copyToByteArray(sectionEmitLights[i], 0);
}
- sectionEmitLights[i] = new byte[2048];
- System.arraycopy(cs[i].k().a, 0, sectionEmitLights[i], 0, 2048); // Should be getBlockLight
+ // Spigot end
}
}
--
1.8.1-rc2