diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java index 68fe3c90b9e4..36d1fd32561f 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java @@ -98,28 +98,54 @@ public byte getByte( long index, int offset ) @Override public short getShort( long index, int offset ) { - return UnsafeUtil.getShort( address( index, offset ) ); + return getShort( address( index, offset ) ); + } + + private short getShort( long p ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getShort( p ); + } + + return UnsafeUtil.getShortByteWiseLittleEndian( p ); } @Override public int getInt( long index, int offset ) { - return UnsafeUtil.getInt( address( index, offset ) ); + return getInt( address( index, offset ) ); + } + + private int getInt( long p ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getInt( p ); + } + + return UnsafeUtil.getIntByteWiseLittleEndian( p ); } @Override public long get6ByteLong( long index, int offset ) { long address = address( index, offset ); - long low4b = (UnsafeUtil.getInt( address )) & 0xFFFFFFFFL; - long high2b = UnsafeUtil.getShort( address + Integer.BYTES ); + long low4b = getInt( address ) & 0xFFFFFFFFL; + long high2b = getShort( address + Integer.BYTES ); return low4b | (high2b << 32); } @Override public long getLong( long index, int offset ) { - return UnsafeUtil.getLong( address( index, offset ) ); + long p = address( index, offset ); + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getLong( p ); + } + + return UnsafeUtil.getLongByteWiseLittleEndian( p ); } @Override @@ -141,27 +167,59 @@ public void setByte( long index, int offset, byte value ) @Override public void setShort( long index, int offset, short value ) { - UnsafeUtil.putShort( address( index, offset ), value ); + putShort( address( index, offset ), value ); + } + + private void putShort( long p, short value ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putShort( p, value ); + } + else + { + UnsafeUtil.putShortByteWiseLittleEndian( p, value ); + } } @Override public void setInt( long index, int offset, int value ) { - UnsafeUtil.putInt( address( index, offset ), value ); + putInt( address( index, offset ), value ); + } + + private void putInt( long p, int value ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putInt( p, value ); + } + else + { + UnsafeUtil.putIntByteWiseLittleEndian( p, value ); + } } @Override public void set6ByteLong( long index, int offset, long value ) { long address = address( index, offset ); - UnsafeUtil.putInt( address, (int) value ); - UnsafeUtil.putShort( address + Integer.BYTES, (short) (value >>> 32) ); + putInt( address, (int) value ); + putShort( address + Integer.BYTES, (short) (value >>> 32) ); } @Override public void setLong( long index, int offset, long value ) { - UnsafeUtil.putLong( address( index, offset ), value ); + long p = address( index, offset ); + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putLong( p, value ); + } + else + { + UnsafeUtil.putLongByteWiseLittleEndian( p, value ); + } } private long address( long index, int offset ) diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java index a2fbebe66263..2880a736d2ba 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapNumberArray.java @@ -23,6 +23,7 @@ public abstract class OffHeapNumberArray> extends BaseNumberArray { + private final long allocatedAddress; protected final long address; protected final long length; private boolean closed; @@ -32,7 +33,23 @@ protected OffHeapNumberArray( long length, int itemSize, long base ) super( itemSize, base ); UnsafeUtil.assertHasUnsafe(); this.length = length; - this.address = UnsafeUtil.allocateMemory( length * itemSize ); + + long dataSize = length * itemSize; + boolean itemSizeIsPowerOfTwo = Integer.bitCount( itemSize ) == 1; + if ( UnsafeUtil.allowUnalignedMemoryAccess || !itemSizeIsPowerOfTwo ) + { + // we can end up here even if we require aligned memory access. Reason is that item size + // isn't power of two anyway and so we have to fallback to safer means of accessing the memory, + // i.e. byte for byte. + this.allocatedAddress = this.address = UnsafeUtil.allocateMemory( dataSize ); + } + else + { + // the item size is a power of two and we're required to access memory aligned + // so we can allocate a bit more to ensure we can get an aligned memory address to start from. + this.allocatedAddress = UnsafeUtil.allocateMemory( dataSize + itemSize - 1 ); + this.address = UnsafeUtil.alignedMemory( allocatedAddress, itemSize ); + } } @Override @@ -55,7 +72,7 @@ public void close() if ( length > 0 ) { // Allocating 0 bytes actually returns address 0 - UnsafeUtil.free( address ); + UnsafeUtil.free( allocatedAddress ); } closed = true; } diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java index 859f89064da6..72f43d63e2fb 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyLongValueUnsafeTable.java @@ -19,8 +19,6 @@ */ package org.neo4j.collection.primitive.hopscotch; -import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; - public class LongKeyLongValueUnsafeTable extends UnsafeTable { public LongKeyLongValueUnsafeTable( int capacity ) @@ -31,21 +29,21 @@ public LongKeyLongValueUnsafeTable( int capacity ) @Override protected long internalKey( long keyAddress ) { - return UnsafeUtil.getLong( keyAddress ); + return alignmentSafeGetLongAsTwoInts( keyAddress ); } @Override protected void internalPut( long keyAddress, long key, long[] value ) { - UnsafeUtil.putLong( keyAddress, key ); - UnsafeUtil.putLong( keyAddress+8, value[0] ); + alignmentSafePutLongAsTwoInts( keyAddress, key ); + alignmentSafePutLongAsTwoInts( keyAddress + 8, value[0] ); } @Override protected long[] internalRemove( long keyAddress ) { - valueMarker[0] = UnsafeUtil.getLong( keyAddress+8 ); - UnsafeUtil.putLong( keyAddress, -1 ); + valueMarker[0] = alignmentSafeGetLongAsTwoInts( keyAddress+8 ); + alignmentSafePutLongAsTwoInts( keyAddress, -1 ); return valueMarker; } @@ -53,8 +51,8 @@ protected long[] internalRemove( long keyAddress ) public long[] putValue( int index, long[] value ) { long valueAddress = valueAddress( index ); - long oldValue = UnsafeUtil.getLong( valueAddress ); - UnsafeUtil.putLong( valueAddress, value[0] ); + long oldValue = alignmentSafeGetLongAsTwoInts( valueAddress ); + alignmentSafePutLongAsTwoInts( valueAddress, value[0] ); return pack( oldValue ); } @@ -72,7 +70,7 @@ private long valueAddress( int index ) @Override public long[] value( int index ) { - long value = UnsafeUtil.getLong( valueAddress( index ) ); + long value = alignmentSafeGetLongAsTwoInts( valueAddress( index ) ); return pack( value ); } diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java index 57d11a41202f..97cb55b62943 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/LongKeyUnsafeTable.java @@ -19,8 +19,6 @@ */ package org.neo4j.collection.primitive.hopscotch; -import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; - public class LongKeyUnsafeTable extends UnsafeTable { public LongKeyUnsafeTable( int capacity, VALUE valueMarker ) @@ -31,13 +29,13 @@ public LongKeyUnsafeTable( int capacity, VALUE valueMarker ) @Override protected long internalKey( long keyAddress ) { - return UnsafeUtil.getLong( keyAddress ); + return alignmentSafeGetLongAsTwoInts( keyAddress ); } @Override protected void internalPut( long keyAddress, long key, VALUE value ) { - UnsafeUtil.putLong( keyAddress, key ); + alignmentSafePutLongAsTwoInts( keyAddress, key ); } @Override diff --git a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java index eac3bd746246..a3eb7037c102 100644 --- a/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java +++ b/community/primitive-collections/src/main/java/org/neo4j/collection/primitive/hopscotch/UnsafeTable.java @@ -19,7 +19,6 @@ */ package org.neo4j.collection.primitive.hopscotch; - import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; public abstract class UnsafeTable extends PowerOfTwoQuantizedTable @@ -27,6 +26,9 @@ public abstract class UnsafeTable extends PowerOfTwoQuantizedTable private final int bytesPerKey; private final int bytesPerEntry; private final long dataSize; + // address which should be free when closing + private final long allocatedAddress; + // address which should be used to access the table, the address where the table actually starts at private final long address; protected final VALUE valueMarker; @@ -38,7 +40,39 @@ protected UnsafeTable( int capacity, int bytesPerKey, VALUE valueMarker ) this.bytesPerEntry = 4+bytesPerKey; this.valueMarker = valueMarker; this.dataSize = (long)this.capacity*bytesPerEntry; - this.address = UnsafeUtil.allocateMemory( dataSize ); + + // Below is a piece of code which ensures that allocated memory is aligned to 4-byte boundary + // if memory system requires aligned memory access. The reason we pick 4-byte boundary is that + // it's the lowest common denominator and the size of our hop-bits field for every entry. + // So even for a table which would only deal with, say longs (8-byte), it would still need to + // read and write 4-byte hop-bits fields. Therefore this table can, if required to, read anything + // bigger than 4-byte fields as multiple 4-byte fields. This way it can play well with aligned + // memory access requirements. + + assert bytesPerEntry % Integer.BYTES == 0 : "Bytes per entry needs to be divisible by 4, this constraint " + + "is checked because on memory systems requiring aligned memory access this would otherwise break."; + + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + this.allocatedAddress = this.address = UnsafeUtil.allocateMemory( dataSize ); + } + else + { + // There's an assertion above also verifying this, but it's only an actual problem if our memory system + // requires aligned access, which seems to be the case right here and now. + if ( (bytesPerEntry % Integer.BYTES) != 0 ) + { + throw new IllegalArgumentException( "Memory system requires aligned memory access and " + + getClass().getSimpleName() + " was designed to cope with this requirement by " + + "being able to accessing data in 4-byte chunks, if needed to. " + + "Although this table tried to be constructed with bytesPerKey:" + bytesPerKey + + " yielding a bytesPerEntry:" + bytesPerEntry + ", which isn't 4-byte aligned." ); + } + + this.allocatedAddress = UnsafeUtil.allocateMemory( dataSize + Integer.BYTES - 1 ); + this.address = UnsafeUtil.alignedMemory( allocatedAddress, Integer.BYTES ); + } + clearMemory(); } @@ -156,6 +190,33 @@ public void removeHopBit( int index, int hd ) @Override public void close() { - UnsafeUtil.free( address ); + UnsafeUtil.free( allocatedAddress ); + } + + protected static void alignmentSafePutLongAsTwoInts( long address, long value ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + UnsafeUtil.putLong( address, value ); + } + else + { + // See javadoc in constructor as to why we do this + UnsafeUtil.putInt( address, (int) value ); + UnsafeUtil.putInt( address + Integer.BYTES, (int) (value >>> Integer.SIZE) ); + } + } + + protected static long alignmentSafeGetLongAsTwoInts( long address ) + { + if ( UnsafeUtil.allowUnalignedMemoryAccess ) + { + return UnsafeUtil.getLong( address ); + } + + // See javadoc in constructor as to why we do this + long lsb = UnsafeUtil.getInt( address ) & 0xFFFFFFFFL; + long msb = UnsafeUtil.getInt( address + Integer.BYTES ) & 0xFFFFFFFFL; + return lsb | (msb << Integer.SIZE); } } diff --git a/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java b/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java index 9c266dd5fe95..3d46a3774fe4 100644 --- a/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java +++ b/community/primitive-collections/src/test/java/org/neo4j/collection/primitive/hopscotch/BasicTableTest.java @@ -25,11 +25,15 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Random; import org.neo4j.collection.primitive.Primitive; import static org.junit.Assert.assertEquals; import static org.junit.Assume.assumeTrue; + +import static java.lang.System.currentTimeMillis; + import static org.neo4j.collection.primitive.Primitive.VALUE_MARKER; @RunWith( Parameterized.class ) @@ -37,6 +41,9 @@ public class BasicTableTest { private final TableFactory factory; + private static final long seed = currentTimeMillis(); + private static final Random random = new Random( seed ); + @Parameterized.Parameters public static Collection data() { @@ -138,7 +145,7 @@ public boolean supportsLongs() @Override public Object sampleValue() { - return new int[] {4}; + return new int[] {random.nextInt( Integer.MAX_VALUE )}; } } } ); result.add( new Object[] { new TableFactory() @@ -158,7 +165,7 @@ public boolean supportsLongs() @Override public Object sampleValue() { - return new long[] {1458489572354L}; + return new long[] {Math.abs( random.nextLong() )}; } } } ); result.add( new Object[] { new TableFactory() @@ -178,7 +185,7 @@ public boolean supportsLongs() @Override public Object sampleValue() { - return new long[] {1458489572354L}; + return new long[] {Math.abs( random.nextLong() )}; } } } ); return result; diff --git a/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java b/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java index ed11afada9ff..246d488e22d2 100644 --- a/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java +++ b/community/unsafe/src/main/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtil.java @@ -332,6 +332,33 @@ public static long allocateMemory( long sizeInBytes ) return pointer; } + /** + * Returns address pointer equal to or slightly after the given {@code pointer}. + * The returned pointer as aligned with {@code alignBy} such that {@code pointer % alignBy == 0}. + * The given pointer should be allocated with at least the requested size + {@code alignBy - 1}, + * where the additional bytes will serve as padding for the worst case where the start of the usable + * area of the allocated memory will need to be shifted at most {@code alignBy - 1} bytes to the right. + * + *

+     * 0   4   8   12  16  20        ; 4-byte alignments
+     * |---|---|---|---|---|         ; memory
+     *        --------===            ; allocated memory (-required, =padding)
+     *         ^------^              ; used memory
+     * 
+ * + * @param pointer pointer to allocated memory from {@link #allocateMemory(long)}. + * @param alignBy power-of-two size to align to, e.g. 4 or 8. + * @return pointer to place inside the allocated memory to consider the effective start of the + * memory, which from that point is aligned by {@code alignBy}. + */ + public static long alignedMemory( long pointer, int alignBy ) + { + assert Integer.bitCount( alignBy ) == 1 : "Requires alignment to be power of 2, but was " + alignBy; + + long misalignment = pointer % alignBy; + return misalignment == 0 ? pointer : pointer + (alignBy - misalignment); + } + /** * Free the memory that was allocated with {@link #allocateMemory}. */ @@ -896,4 +923,113 @@ public static boolean exchangeNativeAccessCheckEnabled( boolean newSetting ) nativeAccessCheckEnabled = newSetting; return previousSetting; } + + /** + * Gets a {@code short} at memory address {@code p} by reading byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values read with this method should have been + * previously put using {@link #putShortByteWiseLittleEndian(long, short)}. + * + * @param p address pointer to start reading at. + * @return the read value, which was read byte for byte. + */ + public static short getShortByteWiseLittleEndian( long p ) + { + short a = (short) (UnsafeUtil.getByte( p ) & 0xFF); + short b = (short) (UnsafeUtil.getByte( p + 1 ) & 0xFF); + return (short) ((b << 8) | a); + } + + /** + * Gets a {@code int} at memory address {@code p} by reading byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values read with this method should have been + * previously put using {@link #putIntByteWiseLittleEndian(long, int)}. + * + * @param p address pointer to start reading at. + * @return the read value, which was read byte for byte. + */ + public static int getIntByteWiseLittleEndian( long p ) + { + int a = UnsafeUtil.getByte( p ) & 0xFF; + int b = UnsafeUtil.getByte( p + 1 ) & 0xFF; + int c = UnsafeUtil.getByte( p + 2 ) & 0xFF; + int d = UnsafeUtil.getByte( p + 3 ) & 0xFF; + return (d << 24) | (c << 16) | (b << 8) | a; + } + + /** + * Gets a {@code long} at memory address {@code p} by reading byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values read with this method should have been + * previously put using {@link #putLongByteWiseLittleEndian(long, long)}. + * + * @param p address pointer to start reading at. + * @return the read value, which was read byte for byte. + */ + public static long getLongByteWiseLittleEndian( long p ) + { + long a = UnsafeUtil.getByte( p ) & 0xFF; + long b = UnsafeUtil.getByte( p + 1 ) & 0xFF; + long c = UnsafeUtil.getByte( p + 2 ) & 0xFF; + long d = UnsafeUtil.getByte( p + 3 ) & 0xFF; + long e = UnsafeUtil.getByte( p + 4 ) & 0xFF; + long f = UnsafeUtil.getByte( p + 5 ) & 0xFF; + long g = UnsafeUtil.getByte( p + 6 ) & 0xFF; + long h = UnsafeUtil.getByte( p + 7 ) & 0xFF; + return (h << 56) | (g << 48) | (f << 40) | (e << 32) | (d << 24) | (c << 16) | (b << 8) | a; + } + + /** + * Puts a {@code short} at memory address {@code p} by writing byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values written with this method should be + * read using {@link #getShortByteWiseLittleEndian(long)}. + * + * @param p address pointer to start writing at. + * @param value value to write byte for byte. + */ + public static void putShortByteWiseLittleEndian( long p, short value ) + { + UnsafeUtil.putByte( p , (byte)( value ) ); + UnsafeUtil.putByte( p + 1, (byte)( value >> 8 ) ); + } + + /** + * Puts a {@code int} at memory address {@code p} by writing byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values written with this method should be + * read using {@link #getIntByteWiseLittleEndian(long)}. + * + * @param p address pointer to start writing at. + * @param value value to write byte for byte. + */ + public static void putIntByteWiseLittleEndian( long p, int value ) + { + UnsafeUtil.putByte( p , (byte)( value ) ); + UnsafeUtil.putByte( p + 1, (byte)( value >> 8 ) ); + UnsafeUtil.putByte( p + 2, (byte)( value >> 16 ) ); + UnsafeUtil.putByte( p + 3, (byte)( value >> 24 ) ); + } + + /** + * Puts a {@code long} at memory address {@code p} by writing byte for byte, instead of the whole value + * in one go. This can be useful, even necessary in some scenarios where {@link #allowUnalignedMemoryAccess} + * is {@code false} and {@code p} isn't aligned properly. Values written with this method should be + * read using {@link #getShortByteWiseLittleEndian(long)}. + * + * @param p address pointer to start writing at. + * @param value value to write byte for byte. + */ + public static void putLongByteWiseLittleEndian( long p, long value ) + { + UnsafeUtil.putByte( p , (byte)( value ) ); + UnsafeUtil.putByte( p + 1, (byte)( value >> 8 ) ); + UnsafeUtil.putByte( p + 2, (byte)( value >> 16 ) ); + UnsafeUtil.putByte( p + 3, (byte)( value >> 24 ) ); + UnsafeUtil.putByte( p + 4, (byte)( value >> 32 ) ); + UnsafeUtil.putByte( p + 5, (byte)( value >> 40 ) ); + UnsafeUtil.putByte( p + 6, (byte)( value >> 48 ) ); + UnsafeUtil.putByte( p + 7, (byte)( value >> 56 ) ); + } } diff --git a/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java b/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java index 7d3ceba9a90b..a6ffdea3c127 100644 --- a/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java +++ b/community/unsafe/src/test/java/org/neo4j/unsafe/impl/internal/dragons/UnsafeUtilTest.java @@ -28,9 +28,12 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static java.lang.System.currentTimeMillis; + import static org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil.*; public class UnsafeUtilTest @@ -230,12 +233,12 @@ public void mustSupportReadingAndWritingOfPrimitivesToMemory() throws Exception setMemory( address, 8, (byte) 0 ); assertThat( getShortVolatile( address ), is( (short) 0 ) ); - putFloat( address, (float) 1 ); + putFloat( address, 1 ); assertThat( getFloat( address ), is( (float) 1 ) ); setMemory( address, 8, (byte) 0 ); assertThat( getFloat( address ), is( (float) 0 ) ); - putFloatVolatile( address, (float) 1 ); + putFloatVolatile( address, 1 ); assertThat( getFloatVolatile( address ), is( (float) 1 ) ); setMemory( address, 8, (byte) 0 ); assertThat( getFloatVolatile( address ), is( (float) 0 ) ); @@ -458,4 +461,71 @@ public void directByteBufferCreationAndInitialisation() throws Exception free( address ); } } + + @Test + public void shouldAlignMemoryTo4ByteBoundary() throws Exception + { + // GIVEN + long allocatedMemory = currentTimeMillis(); + int alignBy = 4; + + // WHEN + for ( int i = 0; i < 10; i++ ) + { + // THEN + long alignedMemory = UnsafeUtil.alignedMemory( allocatedMemory, alignBy ); + assertTrue( alignedMemory >= allocatedMemory ); + assertEquals( 0, alignedMemory % Integer.BYTES ); + assertTrue( alignedMemory - allocatedMemory <= 3 ); + allocatedMemory++; + } + } + + @Test + public void shouldPutAndGetByteWiseLittleEndianShort() throws Exception + { + // GIVEN + long p = UnsafeUtil.allocateMemory( 2 ); + short value = (short) 0b11001100_10101010; + + // WHEN + UnsafeUtil.putShortByteWiseLittleEndian( p, value ); + short readValue = UnsafeUtil.getShortByteWiseLittleEndian( p ); + + // THEN + UnsafeUtil.free( p ); + assertEquals( value, readValue ); + } + + @Test + public void shouldPutAndGetByteWiseLittleEndianInt() throws Exception + { + // GIVEN + long p = UnsafeUtil.allocateMemory( 4 ); + int value = 0b11001100_10101010_10011001_01100110; + + // WHEN + UnsafeUtil.putIntByteWiseLittleEndian( p, value ); + int readValue = UnsafeUtil.getIntByteWiseLittleEndian( p ); + + // THEN + UnsafeUtil.free( p ); + assertEquals( value, readValue ); + } + + @Test + public void shouldPutAndGetByteWiseLittleEndianLong() throws Exception + { + // GIVEN + long p = UnsafeUtil.allocateMemory( 8 ); + long value = 0b11001100_10101010_10011001_01100110__10001000_01000100_00100010_00010001L; + + // WHEN + UnsafeUtil.putLongByteWiseLittleEndian( p, value ); + long readValue = UnsafeUtil.getLongByteWiseLittleEndian( p ); + + // THEN + UnsafeUtil.free( p ); + assertEquals( value, readValue ); + } }