Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Use fixed buffers for BigString compression and decompression to redu…
…ce GC churn. fixup! Use fixed buffers for BigString compression and decompression to reduce GC churn.
- Loading branch information
Showing
7 changed files
with
116 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,5 @@ static BigString encode(String stringValue) { | |
} | ||
String decode(); | ||
|
||
byte[] getBytes(); | ||
|
||
int length(); | ||
} |
24 changes: 6 additions & 18 deletions
24
code/libraries/big-string/src/main/java/nu/marginalia/bigstring/CompressedBigString.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
code/libraries/big-string/src/main/java/nu/marginalia/bigstring/CompressionBuffer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package nu.marginalia.bigstring; | ||
|
||
import net.jpountz.lz4.LZ4Compressor; | ||
import net.jpountz.lz4.LZ4Factory; | ||
import net.jpountz.lz4.LZ4FastDecompressor; | ||
|
||
import java.nio.ByteBuffer; | ||
|
||
/** Buffers for compression and decompression of strings. | ||
* Operations are synchronized on the buffers. | ||
* <p> | ||
* @see CompressionBufferPool CompressionBufferPool */ | ||
public class CompressionBuffer { | ||
private static final int BUFFER_SIZE = 8_000_000; | ||
private final ByteBuffer buffer; | ||
|
||
private static final LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); | ||
private static final LZ4Compressor compressor = lz4Factory.fastCompressor(); | ||
private static final LZ4FastDecompressor decompressor = lz4Factory.fastDecompressor(); | ||
|
||
|
||
public CompressionBuffer() { | ||
this.buffer = ByteBuffer.allocateDirect(BUFFER_SIZE); | ||
} | ||
|
||
/** | ||
* @param stringValue the string to compress | ||
* @return a compressed version of the string in a newly allocated ByteBuffer | ||
*/ | ||
public synchronized ByteBuffer compress(String stringValue) { | ||
final int splitPoint = stringValue.length() * 2; | ||
|
||
buffer.clear(); | ||
|
||
var rawBuffer = buffer.slice(0, splitPoint); | ||
var compressedBuffer = buffer.slice(splitPoint, BUFFER_SIZE - splitPoint); | ||
|
||
rawBuffer.clear(); | ||
rawBuffer.asCharBuffer().append(stringValue); | ||
|
||
// can't flip here because position and limit is in the CharBuffer representation | ||
rawBuffer.position(0); | ||
rawBuffer.limit(stringValue.length() * 2); | ||
|
||
compressedBuffer.clear(); | ||
compressor.compress(rawBuffer, compressedBuffer); | ||
compressedBuffer.flip(); | ||
|
||
ByteBuffer retBuffer = ByteBuffer.allocate(compressedBuffer.limit()); | ||
retBuffer.put(compressedBuffer); | ||
return retBuffer; | ||
} | ||
|
||
public synchronized String decompress(ByteBuffer encoded, int length, int originalSize) { | ||
buffer.position(0); | ||
buffer.limit(length * 2); | ||
|
||
encoded.position(0); | ||
encoded.limit(originalSize); | ||
|
||
decompressor.decompress(encoded, buffer); | ||
|
||
buffer.flip(); | ||
|
||
return buffer.asCharBuffer().toString(); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
code/libraries/big-string/src/main/java/nu/marginalia/bigstring/CompressionBufferPool.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package nu.marginalia.bigstring; | ||
|
||
import java.util.Arrays; | ||
import java.util.concurrent.ThreadLocalRandom; | ||
|
||
/** To avoid contention on the compression buffer, while keeping allocation churn low, | ||
* we use a pool of buffers, randomly selected allocated upon invocation | ||
* <p> | ||
* @see CompressionBuffer CompressionBuffer | ||
* */ | ||
public class CompressionBufferPool { | ||
private static final int BUFFER_COUNT = 16; | ||
private final CompressionBuffer[] destBuffer; | ||
|
||
public CompressionBufferPool() { | ||
destBuffer = new CompressionBuffer[BUFFER_COUNT]; | ||
Arrays.setAll(destBuffer, i -> new CompressionBuffer()); | ||
} | ||
|
||
/** Get the buffer for the current thread */ | ||
public CompressionBuffer bufferForThread() { | ||
int idx = ThreadLocalRandom.current().nextInt(0, BUFFER_COUNT); | ||
|
||
return destBuffer[idx]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
code/libraries/big-string/src/test/java/nu/marginalia/bigstring/CompressedBigStringTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package nu.marginalia.bigstring; | ||
|
||
import nu.marginalia.bigstring.CompressedBigString; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.junit.jupiter.api.Assertions.*; | ||
|
||
class CompressedBigStringTest { | ||
|
||
@Test | ||
public void testCompressDecompress() { | ||
String testString = "This is a test string that is longer than 64 characters. It should be compressed."; | ||
var bigString = new CompressedBigString(testString); | ||
assertEquals(testString, bigString.decode()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters