package jane.test; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayDeque; import org.apache.mina.core.buffer.AbstractIoBuffer; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.IoBufferAllocator; public final class TestCachedBufferAllocator implements IoBufferAllocator { private static final int DEFAULT_MAX_POOL_SIZE = 8; private static final int DEFAULT_MAX_CACHED_BUFFER_SIZE = 1 << 16; // 64KB // public static final AtomicInteger allocCount = new AtomicInteger(); // public static final AtomicInteger cacheCount = new AtomicInteger(); // public static final AtomicInteger offerCount = new AtomicInteger(); private final int maxPoolSize; private final int maxCachedBufferSize; private final ThreadLocal<ArrayDeque<CachedBuffer>[]> heapBuffers = new TestThreadLocal(); private final ThreadLocal<ArrayDeque<CachedBuffer>[]> directBuffers = new TestThreadLocal(); private final int[] checkPoolSize = new int[32]; private final class TestThreadLocal extends ThreadLocal<ArrayDeque<CachedBuffer>[]> { @Override protected ArrayDeque<CachedBuffer>[] initialValue() { @SuppressWarnings("unchecked") ArrayDeque<CachedBuffer>[] poolMap = new ArrayDeque[32]; poolMap[0] = new ArrayDeque<>(); for(int k = 1; k <= maxCachedBufferSize; k += k) { int i = getIdx(k); poolMap[i] = new ArrayDeque<>(); checkPoolSize[i] = k; } return poolMap; } } public TestCachedBufferAllocator() { this(DEFAULT_MAX_POOL_SIZE, DEFAULT_MAX_CACHED_BUFFER_SIZE); } public TestCachedBufferAllocator(int maxPoolSize, int maxCachedBufferSize) // maxCachedBufferSize must be 2^n { this.maxPoolSize = maxPoolSize; this.maxCachedBufferSize = maxCachedBufferSize; } private static int getIdx(int cap) // cap=2^n:[0,0x40000000] => [0,31] { return (int)((4719556544L * cap) >> 32) & 31; // minimal perfect hash function } @Override public IoBuffer allocate(int requestedCapacity, boolean direct) { if(requestedCapacity < 0) requestedCapacity = 0; int actualCapacity = Integer.highestOneBit(requestedCapacity); if(actualCapacity < requestedCapacity) { actualCapacity += actualCapacity; if(actualCapacity < 0) actualCapacity = requestedCapacity; } IoBuffer buf; if(actualCapacity <= maxCachedBufferSize && (buf = (direct ? directBuffers : heapBuffers).get()[getIdx(actualCapacity)].pollFirst()) != null) { buf.clear(); buf.order(ByteOrder.BIG_ENDIAN); // cacheCount.incrementAndGet(); } else { buf = wrap(direct ? ByteBuffer.allocateDirect(actualCapacity) : ByteBuffer.allocate(actualCapacity)); // allocCount.incrementAndGet(); } buf.limit(requestedCapacity); return buf; } @Override public ByteBuffer allocateNioBuffer(int capacity, boolean direct) { return allocate(capacity, direct).buf(); } @Override public IoBuffer wrap(ByteBuffer nioBuffer) { return new CachedBuffer(nioBuffer); } @Override public void dispose() { } private final class CachedBuffer extends AbstractIoBuffer { private final Thread ownerThread = Thread.currentThread(); private ByteBuffer buf; private CachedBuffer(ByteBuffer bb) { super(TestCachedBufferAllocator.this, bb.capacity()); buf = bb; bb.order(ByteOrder.BIG_ENDIAN); } private CachedBuffer(CachedBuffer parent, ByteBuffer bb) { super(parent); buf = bb; } private void free(ByteBuffer oldBuf) { if(oldBuf == null || oldBuf.capacity() > maxCachedBufferSize || oldBuf.isReadOnly() || isDerived() || Thread.currentThread() != ownerThread) return; int cap = oldBuf.capacity(); int i = getIdx(cap); if(checkPoolSize[i] != cap) return; ArrayDeque<CachedBuffer> pool = (oldBuf.isDirect() ? directBuffers : heapBuffers).get()[i]; if(pool.size() < maxPoolSize) { pool.addFirst(new CachedBuffer(oldBuf)); // offerCount.incrementAndGet(); } } @Override public ByteBuffer buf() { if(buf == null) throw new IllegalStateException("Buffer has been freed already."); return buf; } @Override protected void buf(ByteBuffer bb) { ByteBuffer oldBuf = buf; buf = bb; free(oldBuf); } @Override protected IoBuffer duplicate0() { return new CachedBuffer(this, buf().duplicate()); } @Override protected IoBuffer slice0() { return new CachedBuffer(this, buf().slice()); } @Override protected IoBuffer asReadOnlyBuffer0() { return new CachedBuffer(this, buf().asReadOnlyBuffer()); } @Override public byte[] array() { return buf().array(); } @Override public int arrayOffset() { return buf().arrayOffset(); } @Override public boolean hasArray() { return buf().hasArray(); } @Override public void free() { free(buf); buf = null; } } }