Hi All, I worked on a slab allocation at some point, and while speaking about separation of concerns, I tried to put both together.
Slab allocation basically means that the memory manager will point to several slab, each one split in advance in slices of same size, let say slab1 is composed of 1024 slices of 128 bytes, slab2 256 slices of 512 bytes and slab3 64 slices of 2048 bytes. When I try to store a new byte array, I will search for the smallest slab that can fit the size of the byte array, and store the content on this slice. This is deadly simple to implement, fast at usage but the tradeoff is some memory waste (if your size is not well sized for your business you will have lot of unused bytes in slices). Starting to implement the slab OffHeapMemoryBuffer, I start to find overkill to have a Pointer object every time. Why not having a MemoryManager layer that only deal with ByteBuffer. No expiration, LRU and so on. This would be done by the Caching layer, where storing a value will serialize it and store this alltogether in appropriate class. Please give a chance to the attached patch that implement the simplified MemoryManager layer, with Builder pattern style. Thanks, Benoit.
Index: directmemory-cache/src/test/java/org/apache/directmemory/memory/slab/test/SlabTest.java =================================================================== --- directmemory-cache/src/test/java/org/apache/directmemory/memory/slab/test/SlabTest.java (revision 0) +++ directmemory-cache/src/test/java/org/apache/directmemory/memory/slab/test/SlabTest.java (revision 0) @@ -0,0 +1,238 @@ +package org.apache.directmemory.memory.slab.test; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.nio.ByteBuffer; + +import org.apache.directmemory.memory.slab.SlabMemoryManagerBuilder; +import org.apache.directmemory.memory.slab.SlabMemoryManagerImpl; +import org.apache.directmemory.memory.test.MemoryTestUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SlabTest +{ + + private static final int SMALL_SLICE_SIZE = 64; + private static final int MEDUIM_SLICE_SIZE = 128; + private static final int BIG_SLICE_SIZE = 512; + private static final int SLAB_SIZE = 1024; + + SlabMemoryManagerImpl mms = null; + + @Before + public void init() + { + + final SlabMemoryManagerBuilder slabMemoryManagerBuilder = new SlabMemoryManagerBuilder(); + + slabMemoryManagerBuilder.addSlab().sliceSize( SMALL_SLICE_SIZE ).slabSize( SLAB_SIZE ); // 16 slices + slabMemoryManagerBuilder.addSlab().sliceSize( MEDUIM_SLICE_SIZE ).slabSize( SLAB_SIZE ); // 8 slices + slabMemoryManagerBuilder.addSlab().sliceSize( BIG_SLICE_SIZE ).slabSize( SLAB_SIZE ); // 2 slices + + mms = slabMemoryManagerBuilder.build(); + + Assert.assertNotNull( mms ); + } + + @Test + public void testAllocation() + { + final int smallSize = 12; + final int mediumSize = 100; + final int bigSize1 = 345; + final int bigSize2 = 400; + + + final ByteBuffer bb1 = mms.allocate( smallSize ); + Assert.assertEquals( SMALL_SLICE_SIZE, bb1.capacity() ); + Assert.assertEquals( smallSize, bb1.limit() ); + + final ByteBuffer bb2 = mms.allocate( mediumSize ); + Assert.assertEquals( MEDUIM_SLICE_SIZE, bb2.capacity() ); + Assert.assertEquals( mediumSize, bb2.limit() ); + + final ByteBuffer bb3 = mms.allocate( bigSize1 ); + Assert.assertEquals( BIG_SLICE_SIZE, bb3.capacity() ); + Assert.assertEquals( bigSize1, bb3.limit() ); + + final ByteBuffer bb4 = mms.allocate( bigSize2 ); + Assert.assertEquals( BIG_SLICE_SIZE, bb4.capacity() ); + Assert.assertEquals( bigSize2, bb4.limit() ); + + // No more available slices + Assert.assertNull( mms.allocate( bigSize2 ) ); + + } + + @Test + public void testRetrieval() + { + final int size1 = SMALL_SLICE_SIZE; + + for (int i = 0; i < SLAB_SIZE / SMALL_SLICE_SIZE; i++) + { + final byte[] allocatedPayload = MemoryTestUtils.generateRandomPayload( size1 ); + final ByteBuffer bb = mms.store( allocatedPayload ); + Assert.assertNotNull( bb ); + Assert.assertEquals( size1, bb.limit() ); + Assert.assertEquals( SMALL_SLICE_SIZE, bb.capacity() ); + final byte[] retrievedPayload = mms.retrieve( bb ); + Assert.assertNotNull( retrievedPayload ); + Assert.assertEquals(new String(allocatedPayload), new String(retrievedPayload)); + } + // No more available slices + Assert.assertNull( mms.allocate( size1 ) ); + + + final int size2 = MEDUIM_SLICE_SIZE; + + for (int i = 0; i < SLAB_SIZE / MEDUIM_SLICE_SIZE; i++) + { + final byte[] allocatedPayload = MemoryTestUtils.generateRandomPayload( size2 ); + final ByteBuffer bb = mms.store( allocatedPayload ); + Assert.assertNotNull( bb ); + Assert.assertEquals( size2, bb.limit() ); + Assert.assertEquals( MEDUIM_SLICE_SIZE, bb.capacity() ); + final byte[] retrievedPayload = mms.retrieve( bb ); + Assert.assertNotNull( retrievedPayload ); Assert.assertEquals(new String(allocatedPayload), new String(retrievedPayload)); + } + // No more available slices + Assert.assertNull( mms.allocate( size2 ) ); + + + final int size3 = BIG_SLICE_SIZE; + + for (int i = 0; i < SLAB_SIZE / BIG_SLICE_SIZE; i++) + { + final byte[] allocatedPayload = MemoryTestUtils.generateRandomPayload( size3 ); + final ByteBuffer bb = mms.store( allocatedPayload ); + Assert.assertNotNull( bb ); + Assert.assertEquals( size3, bb.limit() ); + Assert.assertEquals( BIG_SLICE_SIZE, bb.capacity() ); + final byte[] retrievedPayload = mms.retrieve( bb ); + Assert.assertNotNull( retrievedPayload ); Assert.assertEquals(new String(allocatedPayload), new String(retrievedPayload)); + } + // No more available slices + Assert.assertNull( mms.allocate( size3 ) ); + + } + + @Test + public void testOverflowingToBiggerSlab() + { + + final SlabMemoryManagerBuilder slabMemoryManagerBuilder = new SlabMemoryManagerBuilder(); + + slabMemoryManagerBuilder.addSlab().sliceSize( SMALL_SLICE_SIZE ).slabSize( SLAB_SIZE ); // 16 slices + slabMemoryManagerBuilder.addSlab().sliceSize( MEDUIM_SLICE_SIZE ).slabSize( SLAB_SIZE ); // 8 slices + slabMemoryManagerBuilder.allowAllocationToBiggerSlab( true ); + + mms = slabMemoryManagerBuilder.build(); + + Assert.assertNotNull( mms ); + + // Allocate all small slices + final int size1 = SMALL_SLICE_SIZE; + for (int i = 0; i < SLAB_SIZE / SMALL_SLICE_SIZE; i++) + { + final byte[] allocatedPayload = MemoryTestUtils.generateRandomPayload( size1 ); + final ByteBuffer bb = mms.store( allocatedPayload ); + Assert.assertNotNull( bb ); + Assert.assertEquals( size1, bb.limit() ); + Assert.assertEquals( SMALL_SLICE_SIZE, bb.capacity() ); + final byte[] retrievedPayload = mms.retrieve( bb ); + Assert.assertNotNull( retrievedPayload ); + Assert.assertEquals(new String(allocatedPayload), new String(retrievedPayload)); + } + + // Should be able to allocate size to a bigger slice + final ByteBuffer bbb = mms.allocate( size1 ); + Assert.assertNotNull( bbb ); + Assert.assertEquals( size1, bbb.limit() ); + Assert.assertEquals( MEDUIM_SLICE_SIZE, bbb.capacity() ); + + } + + + @Test + public void testBuilder1() + { + final int sliceSize = 37; + + final SlabMemoryManagerBuilder slabMemoryManagerBuilder = new SlabMemoryManagerBuilder(); + + slabMemoryManagerBuilder.addSlab().sliceSize( sliceSize ).slabSize( SLAB_SIZE ).numberOfSegments( 3 ); + + mms = slabMemoryManagerBuilder.build(); + + Assert.assertNotNull( mms ); + + // Allocate all small slices + for (int i = 0; i < SLAB_SIZE / sliceSize; i++) + { + final byte[] allocatedPayload = MemoryTestUtils.generateRandomPayload( sliceSize ); + final ByteBuffer bb = mms.store( allocatedPayload ); + Assert.assertNotNull( bb ); + Assert.assertEquals( sliceSize, bb.limit() ); + Assert.assertEquals( sliceSize, bb.capacity() ); + final byte[] retrievedPayload = mms.retrieve( bb ); + Assert.assertNotNull( retrievedPayload ); + Assert.assertEquals(new String(allocatedPayload), new String(retrievedPayload)); + } + + // Should not be able to allocate sliceSize + final ByteBuffer bbb = mms.allocate( sliceSize ); + Assert.assertNull( bbb ); + } + + @Test + public void testBuilder2() + { + final int sliceSize = 37; + + final SlabMemoryManagerBuilder slabMemoryManagerBuilder = new SlabMemoryManagerBuilder(); + + slabMemoryManagerBuilder.addSlab().sliceSize( sliceSize ).slabSize( SLAB_SIZE ).alignSlab( true ); + + mms = slabMemoryManagerBuilder.build(); + + Assert.assertNotNull( mms ); + + // Allocate all small slices + for (int i = 0; i < SLAB_SIZE / SMALL_SLICE_SIZE; i++) + { + final byte[] allocatedPayload = MemoryTestUtils.generateRandomPayload( sliceSize ); + final ByteBuffer bb = mms.store( allocatedPayload ); + Assert.assertNotNull( bb ); + Assert.assertEquals( sliceSize, bb.limit() ); + Assert.assertEquals( SMALL_SLICE_SIZE, bb.capacity() ); + final byte[] retrievedPayload = mms.retrieve( bb ); + Assert.assertNotNull( retrievedPayload ); + Assert.assertEquals(new String(allocatedPayload), new String(retrievedPayload)); + } + + // Should not be able to allocate sliceSize + final ByteBuffer bbb = mms.allocate( sliceSize ); + Assert.assertNull( bbb ); + } + +} Index: directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryBufferImpl.java =================================================================== --- directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryBufferImpl.java (revision 0) +++ directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryBufferImpl.java (revision 0) @@ -0,0 +1,167 @@ +package org.apache.directmemory.memory.slab; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; + +import org.apache.directmemory.memory.OffHeapMemoryBuffer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * Kind of {@link OffHeapMemoryBuffer} implementation that instantiate {@link ByteBuffer}s of fixed size, called slices. + * + * @author bperroud + * + */ +public class SlabMemoryBufferImpl + +{ + + protected static Logger logger = LoggerFactory.getLogger( SlabMemoryBufferImpl.class ); + + // Collection that keeps track of the parent buffers (segments) where slices are allocated + private final Set<ByteBuffer> segmentsBuffers = new HashSet<ByteBuffer>(); + + // Collection that owns all slice that can be used. + private final Queue<ByteBuffer> freeSliceBuffers = new ConcurrentLinkedQueue<ByteBuffer>(); + + // Size of each slices dividing each segments of the slab + private final int sliceSize; + + // Total size of the current slab + private int slabSize; + + // Tells if one need to keep track of borrowed buffers + private final boolean keepTrackOfUsedSliceBuffers; + + // Collection that keeps track of borrowed buffers + private final Set<ByteBuffer> usedSliceBuffers = Collections + .newSetFromMap( new ConcurrentHashMap<ByteBuffer, Boolean>() ); + + protected Logger getLogger() + { + return logger; + } + + /** + * Constructor. + * @param buffer : the internal buffer + * @param bufferNumber : arbitrary number of the buffer. + */ + SlabMemoryBufferImpl( final int totalSlabSize, final int sliceSize, final int numberOfSegments, + final boolean keepTrackOfUsedPointers ) + { + + this.slabSize = totalSlabSize; + this.sliceSize = sliceSize; + + this.keepTrackOfUsedSliceBuffers = keepTrackOfUsedPointers; + + init( numberOfSegments ); + + } + + protected void init( final int numberOfSegments ) + { + // Compute the size of each segments + int segmentSize = slabSize / numberOfSegments; + // size is rounded down to a multiple of the slice size + segmentSize -= segmentSize % sliceSize; + + for ( int i = numberOfSegments - 1; i >= 0; i-- ) + { + final ByteBuffer segment = ByteBuffer.allocateDirect( segmentSize ); + segmentsBuffers.add( segment ); + + for ( int j = 0; j < segment.capacity(); j += sliceSize ) + { + segment.clear(); + segment.position( j ); + segment.limit( j + sliceSize ); + final ByteBuffer slice = segment.slice(); + freeSliceBuffers.add( slice ); + } + } + } + + private void addFreePointer( final ByteBuffer byteBuffer ) + { + freeSliceBuffers.offer( byteBuffer ); + } + + protected ByteBuffer findFreeBuffer( int capacity ) + { + if ( capacity > sliceSize ) + { + return null; + } + return freeSliceBuffers.poll(); + } + + public void free( ByteBuffer byteBuffer ) + { + + if ( keepTrackOfUsedSliceBuffers && !usedSliceBuffers.remove( byteBuffer ) ) + { + return; + } + + Preconditions.checkArgument( byteBuffer.capacity() == sliceSize ); + + addFreePointer( byteBuffer ); + + } + + public ByteBuffer allocate( int size ) + { + + ByteBuffer allocatedByteBuffer = findFreeBuffer( size ); + + if ( allocatedByteBuffer == null ) + { + return null; + } + + allocatedByteBuffer.clear(); + allocatedByteBuffer.limit( size ); + + if ( keepTrackOfUsedSliceBuffers ) + { + usedSliceBuffers.add( allocatedByteBuffer ); + } + + return allocatedByteBuffer; + + } + + public int getSliceSize() + { + return sliceSize; + } +} Index: directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryManagerImpl.java =================================================================== --- directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryManagerImpl.java (revision 0) +++ directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryManagerImpl.java (revision 0) @@ -0,0 +1,174 @@ +package org.apache.directmemory.memory.slab; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.apache.directmemory.memory.MemoryManager; +import org.apache.directmemory.memory.MemoryManagerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; + +/** + * PoC of {@link MemoryManagerService} backed by slabs of different sizes + * + * @author bperroud + * + */ +public class SlabMemoryManagerImpl + implements ByteMemoryManager +{ + + protected static final Logger logger = LoggerFactory.getLogger( MemoryManager.class ); + + // Internal slabs sorted by sliceSize + private final TreeMap<Integer, SlabMemoryBufferImpl> slabs = new TreeMap<Integer, SlabMemoryBufferImpl>(); + + // Tells if allocation to a bigger slab is allowed when allocation to the matched slab fails + private final boolean allowAllocationToBiggerSlab; + + public SlabMemoryManagerImpl( final List<SlabMemoryBufferImpl> slabsToAdd, final boolean allowAllocationToBiggerSlab ) + { + super(); + + for ( final SlabMemoryBufferImpl slab : slabsToAdd ) + { + this.slabs.put( slab.getSliceSize(), slab ); + } + + this.allowAllocationToBiggerSlab = allowAllocationToBiggerSlab; + + } + + private SlabMemoryBufferImpl getSlabThatMatchTheSize( final int size ) + { + // Find the slab that can carry the wanted size. -1 is used because higherEntry returns a strictly higher entry. + final Map.Entry<Integer, SlabMemoryBufferImpl> entry = slabs.higherEntry( size - 1 ); + + if ( entry != null ) + { + return entry.getValue(); + } + + // If an entry has not been found, this means that no slabs has bigger enough slices to allocate the given size + return null; + } + + public ByteBuffer store( final byte[] payload ) + { + final int size = payload.length; + + final ByteBuffer buffer = allocate( size ); + + // Allocation ok, then write the content + if ( buffer != null ) + { + // Write content in the buffer + write( buffer, payload ); + } + + return buffer; + } + + public void update( final ByteBuffer buffer, byte[] payload ) + { + write( buffer, payload ); + } + + private void write( final ByteBuffer buffer, final byte[] payload ) + { + if ( payload.length > buffer.limit() ) + { + throw new BufferOverflowException(); + } + // Create an independent view of the buffer + final ByteBuffer buf = buffer.duplicate(); + buf.rewind(); + // Write the content in the shared buffer + buf.put( payload ); + } + + public byte[] retrieve( final ByteBuffer buffer ) + { + final ByteBuffer roBuffer = buffer.asReadOnlyBuffer(); + roBuffer.rewind(); + + final byte[] swp = new byte[roBuffer.limit()]; + roBuffer.get( swp ); + + return swp; + } + + public void free( final ByteBuffer buffer ) + { + final int size = buffer.capacity(); + final SlabMemoryBufferImpl slab = getSlabThatMatchTheSize( size ); + + Preconditions.checkState( slab != null, "Hu? The pointer seems to come from another memory manager..." ); + + slab.free( buffer ); + } + + public ByteBuffer allocate( final int size ) + { + final SlabMemoryBufferImpl slab = getSlabThatMatchTheSize( size ); + + if ( slab == null ) + { + // unable to store such big objects + return null; + } + + // Try to allocate the given size + final ByteBuffer buffer = slab.allocate( size ); + + // If allocation succeed, return the buffer + if (buffer != null) + { + return buffer; + } + + // Otherwise we have the option to allow in a bigger slab. + if (!allowAllocationToBiggerSlab) + { + return null; + } + else + { + // We can try to allocate to a bigger slab. + // size + 1 here because getSlabThatMatchTheSize do a size -1 and thus will return the same slab + final int biggerSize = slab.getSliceSize() + 1; + final SlabMemoryBufferImpl biggerSlab = getSlabThatMatchTheSize( biggerSize ); + if (biggerSlab == null) + { + // We were already trying to allocate in the biggest slab + return null; + } + + return biggerSlab.allocate( size ); + } + } +} Index: directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/ByteMemoryManager.java =================================================================== --- directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/ByteMemoryManager.java (revision 0) +++ directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/ByteMemoryManager.java (revision 0) @@ -0,0 +1,69 @@ +package org.apache.directmemory.memory.slab; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.nio.ByteBuffer; + +/** + * + * @author bperroud + * + */ +public interface ByteMemoryManager +{ + + /** + * Allocate data off heap memory, store the payload an return the ByteBuffer, or null if the allocation failed + * + * @param payload + * @return + */ + ByteBuffer store( final byte[] payload ); + + /** + * Update the content of a ByteBuffer with the given payload + * + * @param buffer + * @param payload + */ + void update( final ByteBuffer buffer, byte[] payload ); + + /** + * Return the content of previously stored data + * @param buffer + * @return + */ + byte[] retrieve( final ByteBuffer buffer ); + + /** + * Return the given ByteBuffer making it available for a future usage + * @param buffer + */ + + void free( final ByteBuffer buffer ); + + /** + * Allocate the given size off heap, or return null if the allocation failed. + * @param size + * @return + */ + ByteBuffer allocate( final int size ); + +} Index: directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryBufferBuilder.java =================================================================== --- directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryBufferBuilder.java (revision 0) +++ directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryBufferBuilder.java (revision 0) @@ -0,0 +1,158 @@ +package org.apache.directmemory.memory.slab; + +import com.google.common.base.Preconditions; + +/** + * Slab'style memory buffer builder + * + * @author bperroud + * + */ +public class SlabMemoryBufferBuilder +{ + + // Default alignment mask + private static final int DEFAULT_ALIGN_MASK = 0x3F; // align on 128 bits + + private static final int DEFAULT_NUMBER_OF_SEGMENTS = 1; + + private static final boolean DEFAULT_KEEP_TRACK_OF_BORROWED_BUFFERS = false; + + private Integer slabSize; + + private Integer sliceSize; + + private int numberOfSegments = DEFAULT_NUMBER_OF_SEGMENTS; + + private boolean alignSlab = false; + + private int alignMask = DEFAULT_ALIGN_MASK; + + private boolean keepTrackOfBorrowedBuffers = DEFAULT_KEEP_TRACK_OF_BORROWED_BUFFERS; + + public SlabMemoryBufferBuilder() + { + + } + + /** + * Total size of the slab. This size will be divided in segments and each segments in ByteBuffer of equal size. + * + * @param globalSlabSize + * @return + */ + public SlabMemoryBufferBuilder slabSize( final int globalSlabSize ) + { + this.slabSize = globalSlabSize; + return this; + } + + /** + * Size of the ByteBuffer composing the slab. + * It will not be able to store objects bigger than this size in the slab. + * + * @param sliceSize + * @return + */ + public SlabMemoryBufferBuilder sliceSize( final int sliceSize ) + { + this.sliceSize = sliceSize; + return this; + } + + /** + * Set the number of segments (i.e. ByteBuffers) composing your slab. Increasing this factor is useful to increase concurrency (like concurrencyLevel of ConcurrenHashMap) + * + * @param numberOfSegments + * @return + */ + public SlabMemoryBufferBuilder numberOfSegments( final int numberOfSegments ) + { + this.numberOfSegments = numberOfSegments; + return this; + } + + /** + * Tells if the slab and slice sizes need to be aligned. This could be useful to force slices to be aligned on memory cache lines. + * + * @param alignSlab + * @return + */ + public SlabMemoryBufferBuilder alignSlab( final boolean alignSlab ) + { + this.alignSlab = alignSlab; + return this; + } + + /** + * Set alignment factor. Must be a power of 2. + * When setting a new align mask, {@link #alignSlab(boolean)} is automatically called. + * @param alignMask + * @return + */ + public SlabMemoryBufferBuilder alignMask( final int alignMask ) + { + // Check if alignMask is a power of 2 + Preconditions.checkArgument( isPowerOfTwo( alignMask ), "Align mask must be a power of 2" ); + + this.alignMask = alignMask; + alignSlab( true ); + return this; + } + + /** + * Keep track of borrowed buffers in a special collection. This is mainly for debugging purpose, and tends to add a little overhead of both allocate and free functions + * @param keepTrackOfBorrowedBuffers + * @return + */ + public SlabMemoryBufferBuilder keepTrackOfBorrowedBuffers( final boolean keepTrackOfBorrowedBuffers ) + { + this.keepTrackOfBorrowedBuffers = keepTrackOfBorrowedBuffers; + return this; + } + + public SlabMemoryBufferImpl build() + { + Preconditions.checkNotNull( slabSize, "slabSize need to be set" ); + Preconditions.checkNotNull( sliceSize, "sliceSize need to be set" ); + + if ( alignSlab ) + { + slabSize = align( slabSize ); + sliceSize = align( sliceSize ); + } + + // ensure enough segments of at least one slice can be instantiated + Preconditions.checkElementIndex( sliceSize * numberOfSegments, slabSize, + "slabSize is too small to fit so many segments" ); + + final SlabMemoryBufferImpl buffer = new SlabMemoryBufferImpl( slabSize, sliceSize, numberOfSegments, + keepTrackOfBorrowedBuffers ); + + return buffer; + } + + public int getSliceSize() + { + return sliceSize; + } + + private int align( int numberToAlign ) + { + final int alignedNumber; + if ( numberToAlign > alignMask ) + { + alignedNumber = numberToAlign & ~alignMask; + } + else + { + alignedNumber = alignMask + 1; + } + return alignedNumber; + } + + private static boolean isPowerOfTwo( int n ) + { + return ( ( n > 0 ) && ( n & ( n - 1 ) ) == 0 ); + } +} Index: directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryManagerBuilder.java =================================================================== --- directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryManagerBuilder.java (revision 0) +++ directmemory-cache/src/main/java/org/apache/directmemory/memory/slab/SlabMemoryManagerBuilder.java (revision 0) @@ -0,0 +1,94 @@ +package org.apache.directmemory.memory.slab; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import com.google.common.base.Preconditions; + +/** + * Slab'style memory manager builder. + * + * @author bperroud + * + */ +public class SlabMemoryManagerBuilder +{ + + private static final boolean DEFAULT_ALLOW_ALLOCATION_TO_BIGGER_SLAB = false; + + private final List<SlabMemoryBufferBuilder> slabBuilders = new ArrayList<SlabMemoryBufferBuilder>(); + + private boolean allowAllocationToBiggerSlab = DEFAULT_ALLOW_ALLOCATION_TO_BIGGER_SLAB; + + public SlabMemoryManagerBuilder() + { + + } + + /** + * Tells if allocation to a bigger slab is allowed in case of allocation failure to the smallest matching slab. + * + * @param allowAllocationToBiggerSlab + * @return + */ + public SlabMemoryManagerBuilder allowAllocationToBiggerSlab( final boolean allowAllocationToBiggerSlab ) + { + this.allowAllocationToBiggerSlab = allowAllocationToBiggerSlab; + return this; + } + + /** + * Add a slab to the current memory manager, and return a {@link SlabMemoryBufferBuilder} to be able to setup it. + * + * @return + */ + public SlabMemoryBufferBuilder addSlab() + { + final SlabMemoryBufferBuilder slabBuilder = new SlabMemoryBufferBuilder(); + slabBuilders.add( slabBuilder ); + return slabBuilder; + } + + public SlabMemoryManagerImpl build() + { + Preconditions.checkArgument( !slabBuilders.isEmpty(), "You should setup at least 1 slab" ); + + final List<SlabMemoryBufferImpl> slabs = new ArrayList<SlabMemoryBufferImpl>( slabBuilders.size() ); + + // Check that slabs has all different sizes + final Set<Integer> slabSliceSizes = new HashSet<Integer>(); + + for ( final SlabMemoryBufferBuilder slabBuilder : slabBuilders ) + { + Preconditions.checkState( !slabSliceSizes.contains( slabBuilder.getSliceSize() ), "Slabs need to be all different regarding slice size" ); + SlabMemoryBufferImpl slab = slabBuilder.build(); + slabs.add( slab ); + slabSliceSizes.add( slab.getSliceSize() ); + } + + final SlabMemoryManagerImpl mms = new SlabMemoryManagerImpl( slabs, allowAllocationToBiggerSlab ); + + return mms; + } +}
