From 35fdd7249648ecae32140276fc73bd6ac33fe6ec Mon Sep 17 00:00:00 2001 From: Balz Date: Sun, 2 Apr 2017 16:44:27 -0700 Subject: [PATCH] Make direct buffer allocation optional This is a minor refactoring of all codecs that directly allocate Java `ByteBuffers`: instead of making a direct call to `ByteBuffer#allocate()`, the codec invokes a protected method `makeBuffer(size)`. This allows a user to subclass the codec and override the method to customize the buffer allocation. In particular, this enables users to use heap-buffers and/or buffer pooling. The latter is essential for reducing memory churn. In a JVM benchmark (not included) on some real-world data, this refactoring did not decrease performance. (Notice that dropping `final` from some classes does not stop the JVM from inlining, such as in [monomorphic callsites](https://shipilev.net/blog/2015/black-magic-method-dispatch/).) --- .../DeltaZigzagVariableByte.java | 15 +++++++++++++-- .../me/lemire/integercompression/FastPFOR.java | 15 +++++++++++++-- .../me/lemire/integercompression/FastPFOR128.java | 15 +++++++++++++-- .../lemire/integercompression/VariableByte.java | 12 +++++++++++- .../differential/IntegratedVariableByte.java | 14 ++++++++++++-- 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/main/java/me/lemire/integercompression/DeltaZigzagVariableByte.java b/src/main/java/me/lemire/integercompression/DeltaZigzagVariableByte.java index 1988d47..4b2f896 100644 --- a/src/main/java/me/lemire/integercompression/DeltaZigzagVariableByte.java +++ b/src/main/java/me/lemire/integercompression/DeltaZigzagVariableByte.java @@ -13,7 +13,7 @@ * * @author MURAOKA Taro http://github.com/koron */ -public final class DeltaZigzagVariableByte implements IntegerCODEC { +public class DeltaZigzagVariableByte implements IntegerCODEC { @Override public String toString() { @@ -27,7 +27,7 @@ public void compress(int[] inBuf, IntWrapper inPos, int inLen, return; } - ByteBuffer byteBuf = ByteBuffer.allocateDirect(inLen * 5 + 3); + ByteBuffer byteBuf = makeBuffer(inLen * 5 + 3); DeltaZigzagEncoding.Encoder ctx = new DeltaZigzagEncoding.Encoder(0); // Delta+Zigzag+VariableByte encoding. @@ -127,4 +127,15 @@ public void uncompress(int[] inBuf, IntWrapper inPos, int inLen, outPos.set(op); inPos.set(inPosLast); } + + /** + * Creates a new buffer of the requested size. + * + * In case you need a different way to allocate buffers, you can override this method + * with a custom behavior. The default implementation allocates a new Java direct + * {@link ByteBuffer} on each invocation. + */ + protected ByteBuffer makeBuffer(int sizeInBytes) { + return ByteBuffer.allocateDirect(sizeInBytes); + } } diff --git a/src/main/java/me/lemire/integercompression/FastPFOR.java b/src/main/java/me/lemire/integercompression/FastPFOR.java index d31a4da..36226c0 100644 --- a/src/main/java/me/lemire/integercompression/FastPFOR.java +++ b/src/main/java/me/lemire/integercompression/FastPFOR.java @@ -38,7 +38,7 @@ * * @author Daniel Lemire */ -public final class FastPFOR implements IntegerCODEC,SkippableIntegerCODEC { +public class FastPFOR implements IntegerCODEC,SkippableIntegerCODEC { final static int OVERHEAD_OF_EACH_EXCEPT = 8; /** * @@ -68,7 +68,7 @@ public final class FastPFOR implements IntegerCODEC,SkippableIntegerCODEC { private FastPFOR(int pagesize) { pageSize = pagesize; // Initiate arrrays. - byteContainer = ByteBuffer.allocateDirect(3 * pageSize + byteContainer = makeBuffer(3 * pageSize / BLOCK_SIZE + pageSize); byteContainer.order(ByteOrder.LITTLE_ENDIAN); for (int k = 1; k < dataTobePacked.length; ++k) @@ -329,4 +329,15 @@ public void uncompress(int[] in, IntWrapper inpos, int inlength, int[] out, public String toString() { return this.getClass().getSimpleName(); } + + /** + * Creates a new buffer of the requested size. + * + * In case you need a different way to allocate buffers, you can override this method + * with a custom behavior. The default implementation allocates a new Java direct + * {@link ByteBuffer} on each invocation. + */ + protected ByteBuffer makeBuffer(int sizeInBytes) { + return ByteBuffer.allocateDirect(sizeInBytes); + } } diff --git a/src/main/java/me/lemire/integercompression/FastPFOR128.java b/src/main/java/me/lemire/integercompression/FastPFOR128.java index c1efa94..b124072 100644 --- a/src/main/java/me/lemire/integercompression/FastPFOR128.java +++ b/src/main/java/me/lemire/integercompression/FastPFOR128.java @@ -21,7 +21,7 @@ * * @author Daniel Lemire */ -public final class FastPFOR128 implements IntegerCODEC,SkippableIntegerCODEC { +public class FastPFOR128 implements IntegerCODEC,SkippableIntegerCODEC { final static int OVERHEAD_OF_EACH_EXCEPT = 8; /** * @@ -50,7 +50,7 @@ public final class FastPFOR128 implements IntegerCODEC,SkippableIntegerCODEC { public FastPFOR128(int pagesize) { pageSize = pagesize; // Initiate arrrays. - byteContainer = ByteBuffer.allocateDirect(3 * pageSize + byteContainer = makeBuffer(3 * pageSize / BLOCK_SIZE + pageSize); byteContainer.order(ByteOrder.LITTLE_ENDIAN); for (int k = 1; k < dataTobePacked.length; ++k) @@ -310,4 +310,15 @@ public void uncompress(int[] in, IntWrapper inpos, int inlength, int[] out, public String toString() { return this.getClass().getSimpleName(); } + + /** + * Creates a new buffer of the requested size. + * + * In case you need a different way to allocate buffers, you can override this method + * with a custom behavior. The default implementation allocates a new Java direct + * {@link ByteBuffer} on each invocation. + */ + protected ByteBuffer makeBuffer(int sizeInBytes) { + return ByteBuffer.allocateDirect(sizeInBytes); + } } diff --git a/src/main/java/me/lemire/integercompression/VariableByte.java b/src/main/java/me/lemire/integercompression/VariableByte.java index 8e3ce12..5b25c43 100644 --- a/src/main/java/me/lemire/integercompression/VariableByte.java +++ b/src/main/java/me/lemire/integercompression/VariableByte.java @@ -39,7 +39,7 @@ public void headlessCompress(int[] in, IntWrapper inpos, int inlength, int[] out IntWrapper outpos) { if (inlength == 0) return; - ByteBuffer buf = ByteBuffer.allocateDirect(inlength * 8); + ByteBuffer buf = makeBuffer(inlength * 8); buf.order(ByteOrder.LITTLE_ENDIAN); for (int k = inpos.get(); k < inpos.get() + inlength; ++k) { final long val = in[k] & 0xFFFFFFFFL; // To be consistent with @@ -202,4 +202,14 @@ public void headlessUncompress(int[] in, IntWrapper inpos, int inlength, int[] o inpos.set(p + (s!=0 ? 1 : 0)); } + /** + * Creates a new buffer of the requested size. + * + * In case you need a different way to allocate buffers, you can override this method + * with a custom behavior. The default implementation allocates a new Java direct + * {@link ByteBuffer} on each invocation. + */ + protected ByteBuffer makeBuffer(int sizeInBytes) { + return ByteBuffer.allocateDirect(sizeInBytes); + } } diff --git a/src/main/java/me/lemire/integercompression/differential/IntegratedVariableByte.java b/src/main/java/me/lemire/integercompression/differential/IntegratedVariableByte.java index 4352ebb..626954c 100644 --- a/src/main/java/me/lemire/integercompression/differential/IntegratedVariableByte.java +++ b/src/main/java/me/lemire/integercompression/differential/IntegratedVariableByte.java @@ -38,7 +38,7 @@ public void compress(int[] in, IntWrapper inpos, int inlength, if (inlength == 0) return; int initoffset = 0; - ByteBuffer buf = ByteBuffer.allocateDirect(inlength * 8); + ByteBuffer buf = makeBuffer(inlength * 8); buf.order(ByteOrder.LITTLE_ENDIAN); for (int k = inpos.get(); k < inpos.get() + inlength; ++k) { final long val = (in[k] - initoffset) & 0xFFFFFFFFL; // To be consistent with unsigned integers in C/C++ @@ -187,7 +187,7 @@ public void headlessCompress(int[] in, IntWrapper inpos, int inlength, return; int initoffset = initvalue.get(); initvalue.set(in[inpos.get()+inlength -1]); - ByteBuffer buf = ByteBuffer.allocateDirect(inlength * 8); + ByteBuffer buf = makeBuffer(inlength * 8); buf.order(ByteOrder.LITTLE_ENDIAN); for (int k = inpos.get(); k < inpos.get() + inlength; ++k) { final long val = (in[k] - initoffset) & 0xFFFFFFFFL; // To be consistent with unsigned integers in C/C++ @@ -253,4 +253,14 @@ public void headlessUncompress(int[] in, IntWrapper inpos, int inlength, inpos.set(p + (s!=0 ? 1 : 0)); } + /** + * Creates a new buffer of the requested size. + * + * In case you need a different way to allocate buffers, you can override this method + * with a custom behavior. The default implementation allocates a new Java direct + * {@link ByteBuffer} on each invocation. + */ + protected ByteBuffer makeBuffer(int sizeInBytes) { + return ByteBuffer.allocateDirect(sizeInBytes); + } }