org.xerial.snappy.pool.CachingBufferPool Maven / Gradle / Ivy
package org.xerial.snappy.pool;
import java.lang.ref.SoftReference;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentMap;
/**
* A {@link BufferPool} implementation which caches values at fixed sizes.
*
* Pooled instances are held as {@link SoftReference} to allow GC if necessary.
*
*
* The current fixed sizes are calculated as follows:
*
* - Values < 4KB return 4KB
* - 4KB - 32KB to 2KB
* - 32KB - 512KB to 16KB
* - 512KB - 2MB to 128KB
* - 2MB - 16MB to 512KB
* - 16MB - 128MB to 4MB
* - 128MB - 512MB to 16MB
* - 512MB - 1.5 GB to 128MB
* - Values > 1.5GB return {@link Integer#MAX_VALUE}
*
*
* @author Brett Okken
*/
public final class CachingBufferPool implements BufferPool {
private static interface IntFunction {
public E create(int size);
}
private static final IntFunction ARRAY_FUNCTION = new IntFunction() {
@Override
public byte[] create(int size) {
return new byte[size];
}
};
private static final IntFunction DBB_FUNCTION = new IntFunction() {
@Override
public ByteBuffer create(int size) {
return ByteBuffer.allocateDirect(size);
}
};
private static final CachingBufferPool INSTANCE = new CachingBufferPool();
private final ConcurrentMap>> bytes = new ConcurrentHashMap<>();
private final ConcurrentMap>> buffers = new ConcurrentHashMap<>();
private CachingBufferPool() {
}
/**
* Returns instance of {@link CachingBufferPool} for using cached buffers.
* @return instance of {@link CachingBufferPool} for using cached buffers.
*/
public static BufferPool getInstance() {
return INSTANCE;
}
/**
* {@inheritDoc}
*/
@Override
public byte[] allocateArray(int size) {
if (size <= 0) {
throw new IllegalArgumentException("size is invalid: " + size);
}
return getOrCreate(size, bytes, ARRAY_FUNCTION);
}
/**
* {@inheritDoc}
*/
@Override
public void releaseArray(byte[] buffer) {
if (buffer == null) {
throw new IllegalArgumentException("buffer is null");
}
returnValue(buffer, buffer.length, bytes);
}
/**
* {@inheritDoc}
*/
@Override
public ByteBuffer allocateDirect(int size) {
if (size <= 0) {
throw new IllegalArgumentException("size is invalid: " + size);
}
return getOrCreate(size, buffers, DBB_FUNCTION);
}
/**
* {@inheritDoc}
*/
@Override
public void releaseDirect(ByteBuffer buffer) {
if (buffer == null) {
throw new IllegalArgumentException("buffer is null");
}
buffer.clear();
returnValue(buffer, buffer.capacity(), buffers);
}
private static E getOrCreate(final int size, final ConcurrentMap>> map, final IntFunction creator) {
assert size > 0;
final int adjustedSize = adjustSize(size);
final ConcurrentLinkedDeque> queue = optimisticGetEntry(adjustedSize, map);
SoftReference entry;
while ((entry = queue.pollFirst()) != null) {
final E val = entry.get();
if (val != null) {
return val;
}
}
return creator.create(adjustedSize);
}
/*
* This is package scope to allow direct unit testing.
*/
static int adjustSize(int size) {
assert size > 0;
switch (Integer.numberOfLeadingZeros(size)) {
case 1: // 1GB - 2GB
case 2: // 512MB
//if 512MB - 1.5 GB round to nearest 128 MB (2^27), else Integer.MAX_VALUE
return size <= 0x6000_0000 ? roundToPowers(size, 27) : Integer.MAX_VALUE;
case 3: //256MB
case 4: //128MB
//if 128MB - 512MB, round to nearest 16 MB
return roundToPowers(size, 24);
case 5: // 64MB
case 6: // 32MB
case 7: // 16MB
//if 16MB - 128MB, round to nearest 4MB
return roundToPowers(size, 22);
case 8: // 8MB
case 9: // 4MB
case 10: // 2MB
//if 2MB - 16MB, round to nearest 512KB
return roundToPowers(size, 19);
case 11: // 1MB
case 12: //512KB
//if 512KB - 2MB, round to nearest 128KB
return roundToPowers(size, 17);
case 13: //256KB
case 14: //128KB
case 15: // 64KB
case 16: // 32KB
//if 32KB to 512KB, round to nearest 16KB
return roundToPowers(size, 14);
case 17: // 16KB
case 18: // 8KB
case 19: // 4KB
// if 4KB - 32KB, round to nearest 2KB
return roundToPowers(size, 11);
default:
return 4 * 1024;
}
}
private static int roundToPowers(int number, int bits) {
final int mask = (0x7FFF_FFFF >> bits) << bits;
final int floor = number & mask;
return floor == number ? number : floor + (1 << bits);
}
private static ConcurrentLinkedDeque> optimisticGetEntry(Integer key, ConcurrentMap>> map) {
ConcurrentLinkedDeque> val = map.get(key);
if (val == null) {
map.putIfAbsent(key, new ConcurrentLinkedDeque>());
val = map.get(key);
}
return val;
}
private static void returnValue(E value, Integer size, ConcurrentMap>> map) {
final ConcurrentLinkedDeque> queue = map.get(size);
//no queue will exist if buffer was not originally obtained from this class
if (queue != null) {
//push this value onto deque first so that concurrent request can use it
queue.addFirst(new SoftReference(value));
//purge oldest entries have lost references
SoftReference entry;
boolean lastEmpty = true;
while(lastEmpty && (entry = queue.peekLast()) != null) {
if (entry.get() == null) {
queue.removeLastOccurrence(entry);
} else {
lastEmpty = false;
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "CachingBufferPool [bytes=" + this.bytes + ", buffers=" + this.buffers + "]";
}
}