Skip to content

Commit

Permalink
Combine page id, swapper id and usage stamp into single one single long.
Browse files Browse the repository at this point in the history
Make page list record 32 bytes again.
  • Loading branch information
MishaDemianenko committed Jan 30, 2018
1 parent 35a693f commit 73b21b2
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 27 deletions.
9 changes: 9 additions & 0 deletions community/common/src/main/java/org/neo4j/helpers/Numbers.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ public static short safeCastLongToShort( long value )
return (short) value;
}

public static short safeCastIntToShort( int value )
{
if ( (short) value != value )
{
throw new ArithmeticException( getOverflowMessage( value, Short.TYPE ) );
}
return (short) value;
}

public static byte safeCastLongToByte( long value )
{
if ( (byte) value != value )
Expand Down
19 changes: 19 additions & 0 deletions community/common/src/test/java/org/neo4j/helpers/NumbersTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.junit.rules.ExpectedException;

import static org.junit.Assert.assertEquals;
import static org.neo4j.helpers.Numbers.safeCastIntToShort;
import static org.neo4j.helpers.Numbers.safeCastIntToUnsignedShort;
import static org.neo4j.helpers.Numbers.safeCastLongToByte;
import static org.neo4j.helpers.Numbers.safeCastLongToInt;
Expand Down Expand Up @@ -72,6 +73,15 @@ public void failSafeCastLongToByte()
safeCastLongToByte( Byte.MAX_VALUE + 1 );
}

@Test
public void failSafeCastIntToShort()
{
expectedException.expect( ArithmeticException.class );
expectedException.expectMessage( "Value 32768 is too big to be represented as short" );

safeCastIntToShort( Short.MAX_VALUE + 1 );
}

@Test
public void castLongToInt()
{
Expand Down Expand Up @@ -100,6 +110,15 @@ public void castIntToUnsighedShort()
assertEquals( -1, safeCastIntToUnsignedShort( (Short.MAX_VALUE << 1) + 1 ) );
}

@Test
public void castIntToShort()
{
assertEquals(1, safeCastIntToShort( 1 ));
assertEquals(10, safeCastIntToShort( 10 ));
assertEquals( Short.MAX_VALUE, safeCastIntToShort( Short.MAX_VALUE ) );
assertEquals( Short.MIN_VALUE, safeCastIntToShort( Short.MIN_VALUE ) );
}

@Test
public void castLongToByte()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,28 +42,31 @@
* <tr><th>Bytes</th><th>Use</th></tr>
* <tr><td>8</td><td>Sequence lock word.</td></tr>
* <tr><td>8</td><td>Pointer to the memory page.</td></tr>
* <tr><td>8</td><td>File page id.</td></tr>
* <tr><td>8</td><td>Last modified transaction id.</td></tr>
* <tr><td>2</td><td>Page swapper id.</td></tr>
* <tr><td>5</td><td>File page id.</td></tr>
* <tr><td>1</td><td>Usage stamp. Optimistically incremented; truncated to a max of 4.</td></tr>
* <tr><td>2</td><td>Page swapper id.</td></tr>
* </table>
*/
class PageList
{
private static final boolean forceSlowMemoryClear = flag( PageList.class, "forceSlowMemoryClear", false );

private static final int UNBOUND_LAST_MODIFIED_TX_ID = -1;
private static final int UNSIGNED_BYTE_MASK = 0xFF;
private static final long UNSIGNED_INT_MASK = 0xFFFFFFFFL;

// 40 bits for file page id
private static final long MAX_FILE_PAGE_ID = 0b11111111_11111111_11111111_11111111_11111111L;

private static final int META_DATA_BYTES_PER_PAGE = 64; // to simplify
private static final int META_DATA_BYTES_PER_PAGE = 32;
private static final int OFFSET_LOCK_WORD = 0; // 8 bytes
private static final int OFFSET_ADDRESS = 8; // 8 bytes
private static final int OFFSET_FILE_PAGE_ID = 16; // 8 bytes
private static final int OFFSET_LAST_TX_ID = 24; // 8 bytes
private static final int OFFSET_SWAPPER_ID = 32; // 2 bytes
private static final int OFFSET_USAGE_COUNTER = 34; // 1 byte
// todo it's possible to reduce the overhead of the individual page to just 24 bytes,
// todo because the file page id can be represented with 5 bytes (enough to address 8-4 PBs),
// todo and then the usage counter can use the high bits of that word
private static final int OFFSET_LAST_TX_ID = 16; // 8 bytes
private static final int OFFSET_FILE_PAGE_ID = 24; // 5 bytes
private static final int OFFSET_USAGE_COUNTER = 29; // 1 byte
private static final int OFFSET_SWAPPER_ID = 30; // 2 bytes

// todo we can alternatively also make use of the lower 12 bits of the address field, because
// todo the addresses are page aligned, and we can assume them to be at least 4096 bytes in size.

Expand Down Expand Up @@ -144,17 +147,13 @@ private void clearMemory( long baseAddress, long pageCount )

private void clearMemorySimple( long baseAddress, long pageCount )
{
long address = baseAddress - 8;
long address = baseAddress - Long.BYTES;
for ( long i = 0; i < pageCount; i++ )
{
UnsafeUtil.putLong( address += 8, OffHeapPageLock.initialLockWordWithExclusiveLock() ); // lock word
UnsafeUtil.putLong( address += 8, 0 ); // pointer
UnsafeUtil.putLong( address += 8, PageCursor.UNBOUND_PAGE_ID ); // file page id
UnsafeUtil.putLong( address += 8, 0 ); // rest
UnsafeUtil.putLong( address += 8, 0 ); // rest
UnsafeUtil.putLong( address += 8, 0 ); // rest
UnsafeUtil.putLong( address += 8, 0 ); // rest
UnsafeUtil.putLong( address += 8, 0 ); // rest
UnsafeUtil.putLong( address += Long.BYTES, OffHeapPageLock.initialLockWordWithExclusiveLock() ); // lock word
UnsafeUtil.putLong( address += Long.BYTES, 0 ); // pointer
UnsafeUtil.putLong( address += Long.BYTES, 0 ); // last tx id
UnsafeUtil.putLong( address += Long.BYTES, MAX_FILE_PAGE_ID );
}
}

Expand Down Expand Up @@ -205,7 +204,7 @@ public long deref( int pageId )
public int toId( long pageRef )
{
// >> 5 is equivalent to dividing by 32, META_DATA_BYTES_PER_PAGE.
return (int) ((pageRef - baseAddress) >> 6);
return (int) ((pageRef - baseAddress) >> 5);
}

private long offLastModifiedTransactionId( long pageRef )
Expand Down Expand Up @@ -363,12 +362,22 @@ public boolean decrementUsage( long pageRef )

public long getFilePageId( long pageRef )
{
return UnsafeUtil.getLong( offFilePageId( pageRef ) );
int highByte = UnsafeUtil.getByte( offFilePageId( pageRef ) ) & UNSIGNED_BYTE_MASK;
long lowInt = UnsafeUtil.getInt( offFilePageId( pageRef ) + Byte.BYTES ) & UNSIGNED_INT_MASK;
long filePageId = (((long) highByte) << Integer.SIZE) | lowInt;
return filePageId == MAX_FILE_PAGE_ID ? PageCursor.UNBOUND_PAGE_ID : filePageId;
}

private void setFilePageId( long pageRef, long filePageId )
{
UnsafeUtil.putLong( offFilePageId( pageRef ), filePageId );
if ( filePageId > MAX_FILE_PAGE_ID )
{
throw new IllegalArgumentException(
format( "File page id: %s is bigger then max supported value %s.", filePageId, MAX_FILE_PAGE_ID ) );
}
byte highByte = (byte) (filePageId >> Integer.SIZE);
UnsafeUtil.putByte( offFilePageId( pageRef ), highByte );
UnsafeUtil.putInt( offFilePageId( pageRef ) + Byte.BYTES, (int) filePageId );
}

long getLastModifiedTxId( long pageRef )
Expand All @@ -377,7 +386,7 @@ long getLastModifiedTxId( long pageRef )
}

/**
* @return return last modifier transsaction id and resets it to {@link #UNBOUND_LAST_MODIFIED_TX_ID}
* @return return last modifier transaction id and resets it to {@link #UNBOUND_LAST_MODIFIED_TX_ID}
*/
long getAndResetLastModifiedTransactionId( long pageRef )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@
import org.neo4j.collection.primitive.Primitive;
import org.neo4j.collection.primitive.PrimitiveIntIterator;
import org.neo4j.collection.primitive.PrimitiveIntSet;
import org.neo4j.helpers.Numbers;
import org.neo4j.io.pagecache.PageSwapper;

import static org.neo4j.helpers.Numbers.safeCastLongToShort;

/**
* The SwapperSet maintains the set of allocated {@link PageSwapper}s, and their mapping to swapper ids.
* These swapper ids are a limited resource, so they must eventually be reused as files are mapped and unmapped.
Expand Down Expand Up @@ -98,8 +101,7 @@ synchronized short allocate( PageSwapper swapper )
{
if ( !free.isEmpty() )
{
//TODO safe cast
short id = (short) free.iterator().next();
short id = Numbers.safeCastIntToShort( free.iterator().next() );
free.remove( id );
swapperMappings[id] = new SwapperMapping( id, swapper );
this.swapperMappings = swapperMappings; // Volatile store synchronizes-with loads in getters.
Expand All @@ -108,7 +110,7 @@ synchronized short allocate( PageSwapper swapper )
}

// No free slot was found above, so we extend the array to make room for a new slot.
short id = (short) swapperMappings.length;
short id = safeCastLongToShort( swapperMappings.length );
if ( id + 1 > MAX_SWAPPER_ID )
{
throw new IllegalStateException( "All swapper ids are allocated: " + MAX_SWAPPER_ID );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static void stopExecutor()

protected int recordSize = 9;
protected int maxPages = 20;
protected int pageCachePageSize = 64;
protected int pageCachePageSize = 32;
protected int recordsPerFilePage = pageCachePageSize / recordSize;
protected int recordCount = 25 * maxPages * recordsPerFilePage;
protected int filePageSize = recordsPerFilePage * recordSize;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,20 @@ public void pageMustBeLoadedAndBoundAfterFault() throws Exception
assertTrue( pageList.isBoundTo( pageRef, swapperId, filePageId ) );
}

@Test
public void pageWith5BytesFilePageIdMustBeLoadedAndBoundAfterFault() throws Exception
{
// exclusive lock implied by constructor
short swapperId = 12;
long filePageId = Integer.MAX_VALUE + 1L;
pageList.initBuffer( pageRef );
pageList.fault( pageRef, DUMMY_SWAPPER, swapperId, filePageId, PageFaultEvent.NULL );
assertThat( pageList.getFilePageId( pageRef ), is( filePageId ) );
assertThat( pageList.getSwapperId( pageRef ), is( swapperId ) );
assertTrue( pageList.isLoaded( pageRef ) );
assertTrue( pageList.isBoundTo( pageRef, swapperId, filePageId ) );
}

@Test
public void pageMustBeLoadedAndNotBoundIfFaultThrows() throws Exception
{
Expand Down Expand Up @@ -2047,6 +2061,14 @@ public long write( long filePageId, long bufferAddress ) throws IOException
assertTrue( pageList.validateReadLock( nextPageRef, nextStamp ) );
}

@Test( expected = IllegalArgumentException.class )
public void failToSetHigherThenSupportedFilePageIdOnFault() throws IOException
{
pageList.unlockExclusive( pageRef );
short swapperId = 2;
doFault( swapperId, Long.MAX_VALUE );
}

private void doFault( short swapperId, long filePageId ) throws IOException
{
assertTrue( pageList.tryExclusiveLock( pageRef ) );
Expand Down

0 comments on commit 73b21b2

Please sign in to comment.