From 3f295e152c67d34939c228b3881b28d5144cc9c0 Mon Sep 17 00:00:00 2001 From: Mikhaylo Demianenko Date: Mon, 1 Aug 2016 17:43:36 +0200 Subject: [PATCH] Fixed references in enterprise format Because of variable length encoding of references decoding and encoding or records in enterprise format takes some additional time. To minimize damage for common case of small records possibility of fixed records introduced. From now on in case if record can be encoded in a fixed reference format (criteria based on references that are used in record) it will be used instead of variable-length format. In case if record can't be encoded in fixed references format - default variable-length format will be used. --- .../impl/store/record/AbstractBaseRecord.java | 13 ++ .../highlimit/BaseHighLimitRecordFormat.java | 31 +++- .../format/highlimit/NodeRecordFormat.java | 70 ++++++-- .../highlimit/PropertyRecordFormat.java | 60 ++++++- .../RelationshipGroupRecordFormat.java | 97 +++++++++-- .../highlimit/RelationshipRecordFormat.java | 152 +++++++++++++++--- .../BaseHighLimitRecordFormatTest.java | 6 + .../highlimit/PropertyRecordFormatTest.java | 2 +- 8 files changed, 373 insertions(+), 58 deletions(-) diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java index 9f7f62ae97c1f..6b4ac6820574f 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/store/record/AbstractBaseRecord.java @@ -41,6 +41,7 @@ public abstract class AbstractBaseRecord implements CloneableInPublic private boolean requiresSecondaryUnit; private boolean inUse; private boolean created; + private boolean useFixedReferences; protected AbstractBaseRecord( long id ) { @@ -54,6 +55,7 @@ protected AbstractBaseRecord initialize( boolean inUse ) this.created = false; this.secondaryUnitId = NO_ID; this.requiresSecondaryUnit = false; + this.useFixedReferences = false; return this; } @@ -69,6 +71,7 @@ public void clear() created = false; secondaryUnitId = NO_ID; requiresSecondaryUnit = false; + this.useFixedReferences = false; } public long getId() @@ -143,6 +146,16 @@ public final boolean isCreated() return created; } + public boolean isUseFixedReferences() + { + return useFixedReferences; + } + + public void setUseFixedReferences( boolean useFixedReferences ) + { + this.useFixedReferences = useFixedReferences; + } + @Override public int hashCode() { diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java index 3754fdb595fc5..e24bd6d9ecedf 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormat.java @@ -84,6 +84,7 @@ abstract class BaseHighLimitRecordFormat static final long NULL = Record.NULL_REFERENCE.intValue(); static final int HEADER_BIT_RECORD_UNIT = 0b0000_0010; static final int HEADER_BIT_FIRST_RECORD_UNIT = 0b0000_0100; + private static final int HEADER_BIT_FIXED_REFERENCE = 0b0000_0100; protected BaseHighLimitRecordFormat( Function recordSize, int recordHeaderSize ) { @@ -140,6 +141,7 @@ public void read( RECORD record, PageCursor primaryCursor, RecordLoad mode, int } else { + record.setUseFixedReferences( has( headerByte, HEADER_BIT_FIXED_REFERENCE ) ); doReadInternal( record, primaryCursor, recordSize, headerByte, inUse ); } } @@ -164,7 +166,14 @@ public void write( RECORD record, PageCursor primaryCursor, int recordSize ) ") collides with format-generic header bits"; headerByte = set( headerByte, IN_USE_BIT, record.inUse() ); headerByte = set( headerByte, HEADER_BIT_RECORD_UNIT, record.requiresSecondaryUnit() ); - headerByte = set( headerByte, HEADER_BIT_FIRST_RECORD_UNIT, true ); + if ( record.requiresSecondaryUnit() ) + { + headerByte = set( headerByte, HEADER_BIT_FIRST_RECORD_UNIT, true ); + } + else + { + headerByte = set( headerByte, HEADER_BIT_FIXED_REFERENCE, record.isUseFixedReferences() ); + } primaryCursor.putByte( headerByte ); if ( record.requiresSecondaryUnit() ) @@ -232,18 +241,24 @@ public final void prepare( RECORD record, int recordSize, IdSequence idSequence { if ( record.inUse() ) { - int requiredLength = HEADER_BYTE + requiredDataLength( record ); - boolean requiresSecondaryUnit = requiredLength > recordSize; - record.setRequiresSecondaryUnit( requiresSecondaryUnit ); - if ( record.requiresSecondaryUnit() && !record.hasSecondaryUnitId() ) + record.setUseFixedReferences( canUseFixedReferences( record )); + if ( !record.isUseFixedReferences() ) { - // Allocate a new id at this point, but this is not the time to free this ID the the case where - // this record doesn't need this secondary unit anymore... that needs to be done when applying to store. - record.setSecondaryUnitId( idSequence.nextId() ); + int requiredLength = HEADER_BYTE + requiredDataLength( record ); + boolean requiresSecondaryUnit = requiredLength > recordSize; + record.setRequiresSecondaryUnit( requiresSecondaryUnit ); + if ( record.requiresSecondaryUnit() && !record.hasSecondaryUnitId() ) + { + // Allocate a new id at this point, but this is not the time to free this ID the the case where + // this record doesn't need this secondary unit anymore... that needs to be done when applying to store. + record.setSecondaryUnitId( idSequence.nextId() ); + } } } } + protected abstract boolean canUseFixedReferences( RECORD record ); + /** * Required length of the data in the given record (without the header byte). * diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java index fe62bfe558053..2dc21f525f1f0 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/NodeRecordFormat.java @@ -22,6 +22,7 @@ import java.io.IOException; import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; import org.neo4j.kernel.impl.store.record.NodeRecord; import org.neo4j.kernel.impl.store.record.Record; @@ -69,13 +70,32 @@ protected void doReadInternal( NodeRecord record, PageCursor cursor, int recordS { // Interpret the header byte boolean dense = has( headerByte, DENSE_NODE_BIT ); + if ( record.isUseFixedReferences() ) + { + byte modifiers = cursor.getByte(); + long relModifier = (modifiers & 0b0000_0001L) << 32; + long propModifier = (modifiers & 0b0000_0110L) << 31; - // Now read the rest of the data. The adapter will take care of moving the cursor over to the - // other unit when we've exhausted the first one. - long nextRel = decodeCompressedReference( cursor, headerByte, HAS_RELATIONSHIP_BIT, NULL ); - long nextProp = decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ); - long labelField = decodeCompressedReference( cursor, headerByte, HAS_LABELS_BIT, NULL_LABELS ); - record.initialize( inUse, nextProp, dense, nextRel, labelField ); + long nextRel = cursor.getInt() & 0xFFFFFFFFL; + long nextProp = cursor.getInt() & 0xFFFFFFFFL; + + long lsbLabels = cursor.getInt() & 0xFFFFFFFFL; + long hsbLabels = cursor.getByte() & 0xFF; // so that a negative byte won't fill the "extended" bits with ones. + long labels = lsbLabels | (hsbLabels << 32); + + record.initialize( inUse, + BaseRecordFormat.longFromIntAndMod( nextProp, propModifier ), dense, + BaseRecordFormat.longFromIntAndMod( nextRel, relModifier ), labels ); + } + else + { + // Now read the rest of the data. The adapter will take care of moving the cursor over to the + // other unit when we've exhausted the first one. + long nextRel = decodeCompressedReference( cursor, headerByte, HAS_RELATIONSHIP_BIT, NULL ); + long nextProp = decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ); + long labelField = decodeCompressedReference( cursor, headerByte, HAS_LABELS_BIT, NULL_LABELS ); + record.initialize( inUse, nextProp, dense, nextRel, labelField ); + } } @Override @@ -97,12 +117,44 @@ protected byte headerBits( NodeRecord record ) return header; } + @Override + protected boolean canUseFixedReferences( NodeRecord record ) + { + return (((record.getNextProp() == NULL) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)) && + ((record.getNextRel() == NULL) || ((record.getNextRel() & 0xFFFF_FFFE_0000_0000L) == 0))); + } + @Override protected void doWriteInternal( NodeRecord record, PageCursor cursor ) throws IOException { - encode( cursor, record.getNextRel(), NULL ); - encode( cursor, record.getNextProp(), NULL ); - encode( cursor, record.getLabelField(), NULL_LABELS ); + if ( record.isUseFixedReferences() ) + { + long nextRel = record.getNextRel(); + long nextProp = record.getNextProp(); + + short relModifier = nextRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (short)((nextRel & 0x1_0000_0000L) >> 32); + short propModifier = nextProp == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((nextProp & 0x3_0000_0000L) >> 31); + + // [ ,xxxx] higher bits for rel id + // [xxxx, ] higher bits for prop id + short modifiers = (short) ( relModifier | propModifier ); + + cursor.putByte( (byte) modifiers ); + cursor.putInt( (int) nextRel ); + cursor.putInt( (int) nextProp ); + + // lsb of labels + long labelField = record.getLabelField(); + cursor.putInt( (int) labelField ); + // msb of labels + cursor.putByte( (byte) ((labelField & 0xFF_0000_0000L) >> 32) ); + } + else + { + encode( cursor, record.getNextRel(), NULL ); + encode( cursor, record.getNextProp(), NULL ); + encode( cursor, record.getLabelField(), NULL_LABELS ); + } } } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java index c836303510095..1e4e1d30308a6 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormat.java @@ -23,12 +23,15 @@ import org.neo4j.io.pagecache.PageCursor; import org.neo4j.kernel.impl.store.format.BaseOneByteHeaderRecordFormat; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; import org.neo4j.kernel.impl.store.record.PropertyBlock; import org.neo4j.kernel.impl.store.record.PropertyRecord; +import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.store.record.RecordLoad; import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toAbsolute; import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toRelative; +import static org.neo4j.kernel.impl.store.record.Record.NULL_REFERENCE; /** @@ -68,13 +71,31 @@ public void read( PropertyRecord record, PageCursor cursor, RecordLoad mode, int int offset = cursor.getOffset(); byte headerByte = cursor.getByte(); boolean inUse = isInUse( headerByte ); + record.setUseFixedReferences( has( headerByte, 0b0000_0100 )); if ( mode.shouldLoad( inUse ) ) { int blockCount = headerByte >>> 4; long recordId = record.getId(); - record.initialize( inUse, - toAbsolute( Reference.decode( cursor ), recordId ), - toAbsolute( Reference.decode( cursor ), recordId ) ); + + if (record.isUseFixedReferences()) + { + // since fixed reference limits property reference to 34 bits, 6 bytes is ample. + long prevMod = (cursor.getShort() & 0xFFFFL); + long prevProp = cursor.getInt() & 0xFFFFFFFFL; + long nextMod = (cursor.getShort() & 0xFFFFL); + long nextProp = cursor.getInt() & 0xFFFFFFFFL; + record.initialize( true, + BaseRecordFormat.longFromIntAndMod( prevProp, prevMod << 32 ), + BaseRecordFormat.longFromIntAndMod( nextProp, nextMod << 32 ) ); + //skip 3 bytes before start reading property blocks + cursor.setOffset(cursor.getOffset() + 3); + } + else + { + record.initialize( inUse, + toAbsolute( Reference.decode( cursor ), recordId ), + toAbsolute( Reference.decode( cursor ), recordId ) ); + } if ( (blockCount > record.getBlockCapacity()) | (RECORD_SIZE - (cursor.getOffset() - offset) < blockCount * Long.BYTES) ) { cursor.setCursorException( "PropertyRecord claims to contain more blocks than can fit in a record" ); @@ -93,10 +114,31 @@ public void write( PropertyRecord record, PageCursor cursor, int recordSize ) { if ( record.inUse() ) { - cursor.putByte( (byte) ((record.inUse() ? IN_USE_BIT : 0) | numberOfBlocks( record ) << 4) ); + byte headerByte = (byte) ((record.inUse() ? IN_USE_BIT : 0) | numberOfBlocks( record ) << 4); + boolean canUseFixedReferences = canUseFixedReferences( record ); + record.setUseFixedReferences( canUseFixedReferences ); + headerByte = set( headerByte, 0b0000_0100, canUseFixedReferences ); + cursor.putByte( headerByte ); + long recordId = record.getId(); - Reference.encode( toRelative( record.getPrevProp(), recordId), cursor ); - Reference.encode( toRelative( record.getNextProp(), recordId), cursor ); + + if ( canUseFixedReferences ) + { + // Set up the record header + short prevModifier = record.getPrevProp() == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((record.getPrevProp() & 0xFFFF_0000_0000L) >> 32); + short nextModifier = record.getNextProp() == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (short) ((record.getNextProp() & 0xFFFF_0000_0000L) >> 32); + cursor.putShort( prevModifier ); + cursor.putInt( (int) record.getPrevProp() ); + cursor.putShort( nextModifier ); + cursor.putInt( (int) record.getNextProp() ); + //just stuff 3 bytes + cursor.putBytes(new byte[]{0,0,0}); + } + else + { + Reference.encode( toRelative( record.getPrevProp(), recordId ), cursor ); + Reference.encode( toRelative( record.getNextProp(), recordId ), cursor ); + } for ( PropertyBlock block : record ) { for ( long propertyBlock : block.getValueBlocks() ) @@ -126,4 +168,10 @@ public long getNextRecordReference( PropertyRecord record ) { return record.getNextProp(); } + + private boolean canUseFixedReferences( PropertyRecord record ) + { + return (((record.getNextProp() == NULL_REFERENCE.intValue()) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)) && + ((record.getPrevProp() == NULL_REFERENCE.intValue()) || ((record.getPrevProp() & 0xFFFF_FFFE_0000_0000L) == 0))); + } } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java index 8863e1ac38066..ae258be8b5a0c 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipGroupRecordFormat.java @@ -22,6 +22,8 @@ import java.io.IOException; import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; +import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.store.record.RelationshipGroupRecord; /** @@ -68,13 +70,47 @@ public RelationshipGroupRecord newRecord() protected void doReadInternal( RelationshipGroupRecord record, PageCursor cursor, int recordSize, long headerByte, boolean inUse ) { - record.initialize( inUse, - cursor.getShort() & 0xFFFF, - decodeCompressedReference( cursor, headerByte, HAS_OUTGOING_BIT, NULL ), - decodeCompressedReference( cursor, headerByte, HAS_INCOMING_BIT, NULL ), - decodeCompressedReference( cursor, headerByte, HAS_LOOP_BIT, NULL ), - decodeCompressedReference( cursor ), - decodeCompressedReference( cursor, headerByte, HAS_NEXT_BIT, NULL ) ); + if ( record.isUseFixedReferences() ) + { + + // [ , x] high next bits + // [ , x ] high firstOut bits + // [ , x ] high firstIn bits + // [ ,x ] high firstLoop bits + // [ x, ] high owner bits + long modifiers = cursor.getByte(); + + int type = cursor.getShort() & 0xFFFF; + + long nextLowBits = cursor.getInt() & 0xFFFFFFFFL; + long nextOutLowBits = cursor.getInt() & 0xFFFFFFFFL; + long nextInLowBits = cursor.getInt() & 0xFFFFFFFFL; + long nextLoopLowBits = cursor.getInt() & 0xFFFFFFFFL; + long owningNode = cursor.getInt() & 0xFFFFFFFFL; + + long nextMod = (modifiers & 0b0000_0001) << 32; + long nextOutMod = (modifiers & 0b0000_0010) << 31; + long nextInMod = (modifiers & 0b0000_0100) << 30; + long nextLoopMod = (modifiers & 0b0000_1000) << 29; + long owningNodeMod = (modifiers & 0b0001_0000) << 28; + + record.initialize( inUse, type, + BaseRecordFormat.longFromIntAndMod( nextOutLowBits, nextOutMod ), + BaseRecordFormat.longFromIntAndMod( nextInLowBits, nextInMod ), + BaseRecordFormat.longFromIntAndMod( nextLoopLowBits, nextLoopMod ), + BaseRecordFormat.longFromIntAndMod( owningNode, owningNodeMod ), + BaseRecordFormat.longFromIntAndMod( nextLowBits, nextMod ) ); + } + else + { + record.initialize( inUse, + cursor.getShort() & 0xFFFF, + decodeCompressedReference( cursor, headerByte, HAS_OUTGOING_BIT, NULL ), + decodeCompressedReference( cursor, headerByte, HAS_INCOMING_BIT, NULL ), + decodeCompressedReference( cursor, headerByte, HAS_LOOP_BIT, NULL ), + decodeCompressedReference( cursor ), + decodeCompressedReference( cursor, headerByte, HAS_NEXT_BIT, NULL ) ); + } } @Override @@ -103,11 +139,46 @@ protected int requiredDataLength( RelationshipGroupRecord record ) protected void doWriteInternal( RelationshipGroupRecord record, PageCursor cursor ) throws IOException { - cursor.putShort( (short) record.getType() ); - encode( cursor, record.getFirstOut(), NULL ); - encode( cursor, record.getFirstIn(), NULL ); - encode( cursor, record.getFirstLoop(), NULL ); - encode( cursor, record.getOwningNode() ); - encode( cursor, record.getNext(), NULL ); + if ( record.isUseFixedReferences() ) + { + long nextMod = record.getNext() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getNext() & 0x100000000L) >> 32; + long nextOutMod = record.getFirstOut() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getFirstOut() & 0x100000000L) >> 31; + long nextInMod = record.getFirstIn() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getFirstIn() & 0x100000000L) >> 30; + long nextLoopMod = record.getFirstLoop() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getFirstLoop() & 0x100000000L) >> 29; + long ownerMod = record.getOwningNode() == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (record.getOwningNode() & 0x100000000L) >> 28; + + // [ , x] high next bits + // [ , x ] high firstOut bits + // [ , x ] high firstIn bits + // [ ,x ] high firstLoop bits + // [ x, ] high owner bits + cursor.putByte( (byte) (nextMod | nextOutMod | nextInMod | nextLoopMod | ownerMod) ); + + cursor.putShort( (short) record.getType() ); + cursor.putInt( (int) record.getNext() ); + cursor.putInt( (int) record.getFirstOut() ); + cursor.putInt( (int) record.getFirstIn() ); + cursor.putInt( (int) record.getFirstLoop() ); + cursor.putInt( (int) record.getOwningNode() ); + } + else + { + cursor.putShort( (short) record.getType() ); + encode( cursor, record.getFirstOut(), NULL ); + encode( cursor, record.getFirstIn(), NULL ); + encode( cursor, record.getFirstLoop(), NULL ); + encode( cursor, record.getOwningNode() ); + encode( cursor, record.getNext(), NULL ); + } + } + + @Override + protected boolean canUseFixedReferences( RelationshipGroupRecord record ) + { + return !((record.getNext() != NULL) && ((record.getNext() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getFirstOut() != NULL) && ((record.getFirstOut() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getFirstIn() != NULL) && ((record.getFirstIn() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getFirstLoop() != NULL) && ((record.getFirstLoop() & 0xFFFF_FFFE_0000_0000L) != 0) || + (record.getOwningNode() != NULL) && ((record.getOwningNode() & 0xFFFF_FFFE_0000_0000L) != 0)); } } diff --git a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java index 95c259509ed89..207cd67d1c31b 100644 --- a/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java +++ b/enterprise/kernel/src/main/java/org/neo4j/kernel/impl/store/format/highlimit/RelationshipRecordFormat.java @@ -22,6 +22,8 @@ import java.io.IOException; import org.neo4j.io.pagecache.PageCursor; +import org.neo4j.kernel.impl.store.format.BaseRecordFormat; +import org.neo4j.kernel.impl.store.record.Record; import org.neo4j.kernel.impl.store.record.RelationshipRecord; import static org.neo4j.kernel.impl.store.format.highlimit.Reference.toAbsolute; @@ -76,17 +78,64 @@ protected void doReadInternal( { int type = cursor.getShort() & 0xFFFF; long recordId = record.getId(); - record.initialize( inUse, - decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ), - decodeCompressedReference( cursor ), - decodeCompressedReference( cursor ), - type, - decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_FIRST_CHAIN_BIT, recordId ), - decodeAbsoluteIfPresent( cursor, headerByte, HAS_FIRST_CHAIN_NEXT_BIT, recordId ), - decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_SECOND_CHAIN_BIT, recordId ), - decodeAbsoluteIfPresent( cursor, headerByte, HAS_SECOND_CHAIN_NEXT_BIT, recordId ), - has( headerByte, FIRST_IN_FIRST_CHAIN_BIT ), - has( headerByte, FIRST_IN_SECOND_CHAIN_BIT ) ); + if (record.isUseFixedReferences()) + { + // [ , x] first node higher order bits + // [ , x ] second node high order bits + // [ , x ] first prev high order bits + // [ ,x ] first next high order bits + // [ x, ] second prev high order bits + // [ x , ] second next high order bits + // [xx , ] next prop high order bits + long modifiers = cursor.getByte(); + + long firstNode = cursor.getInt() & 0xFFFFFFFFL; + long firstNodeMod = (modifiers & 0b0000_0001L) << 32; + + long secondNode = cursor.getInt() & 0xFFFFFFFFL; + long secondNodeMod = (modifiers & 0b0000_0010L) << 31; + + long firstPrevRel = cursor.getInt() & 0xFFFFFFFFL; + long firstPrevRelMod = (modifiers & 0b0000_0100L) << 30; + + long firstNextRel = cursor.getInt() & 0xFFFFFFFFL; + long firstNextRelMod = (modifiers & 0b0000_1000L) << 29; + + long secondPrevRel = cursor.getInt() & 0xFFFFFFFFL; + long secondPrevRelMod = (modifiers & 0b0001_0000L) << 28; + + long secondNextRel = cursor.getInt() & 0xFFFFFFFFL; + long secondNextRelMod = (modifiers & 0b0010_0000L) << 27; + + long nextProp = cursor.getInt() & 0xFFFFFFFFL; + long nextPropMod = (modifiers & 0b1100_0000L) << 26; + + record.initialize( inUse, + BaseRecordFormat.longFromIntAndMod( nextProp, nextPropMod ), + BaseRecordFormat.longFromIntAndMod( firstNode, firstNodeMod ), + BaseRecordFormat.longFromIntAndMod( secondNode, secondNodeMod ), + type, + BaseRecordFormat.longFromIntAndMod( firstPrevRel, firstPrevRelMod ), + BaseRecordFormat.longFromIntAndMod( firstNextRel, firstNextRelMod ), + BaseRecordFormat.longFromIntAndMod( secondPrevRel, secondPrevRelMod ), + BaseRecordFormat.longFromIntAndMod( secondNextRel, secondNextRelMod ), + has( headerByte, FIRST_IN_FIRST_CHAIN_BIT ), + has( headerByte, FIRST_IN_SECOND_CHAIN_BIT ) ); + } + else + { + record.initialize( inUse, + decodeCompressedReference( cursor, headerByte, HAS_PROPERTY_BIT, NULL ), + decodeCompressedReference( cursor ), + decodeCompressedReference( cursor ), + type, + decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_FIRST_CHAIN_BIT, recordId ), + decodeAbsoluteIfPresent( cursor, headerByte, HAS_FIRST_CHAIN_NEXT_BIT, recordId ), + decodeAbsoluteOrRelative( cursor, headerByte, FIRST_IN_SECOND_CHAIN_BIT, recordId ), + decodeAbsoluteIfPresent( cursor, headerByte, HAS_SECOND_CHAIN_NEXT_BIT, recordId ), + has( headerByte, FIRST_IN_FIRST_CHAIN_BIT ), + has( headerByte, FIRST_IN_SECOND_CHAIN_BIT ) ); + } } private long decodeAbsoluteOrRelative( PageCursor cursor, long headerByte, int firstInStartBit, long recordId ) @@ -128,22 +177,83 @@ protected void doWriteInternal( RelationshipRecord record, PageCursor cursor ) { cursor.putShort( (short) record.getType() ); long recordId = record.getId(); - encode( cursor, record.getNextProp(), NULL ); - encode( cursor, record.getFirstNode() ); - encode( cursor, record.getSecondNode() ); - - encode( cursor, getFirstPrevReference( record, recordId ) ); - if ( record.getFirstNextRel() != NULL ) + if (record.isUseFixedReferences()) { - encode( cursor, toRelative( record.getFirstNextRel(), recordId ) ); + long firstNode = record.getFirstNode(); + short firstNodeMod = (short)((firstNode & 0x1_0000_0000L) >> 32); + + long secondNode = record.getSecondNode(); + long secondNodeMod = (secondNode & 0x1_0000_0000L) >> 31; + + long firstPrevRel = record.getFirstPrevRel(); + long firstPrevRelMod = firstPrevRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (firstPrevRel & 0x1_0000_0000L) >> 30; + + long firstNextRel = record.getFirstNextRel(); + long firstNextRelMod = firstNextRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (firstNextRel & 0x1_0000_0000L) >> 29; + + long secondPrevRel = record.getSecondPrevRel(); + long secondPrevRelMod = secondPrevRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (secondPrevRel & 0x1_0000_0000L) >> 28; + + long secondNextRel = record.getSecondNextRel(); + long secondNextRelMod = secondNextRel == Record.NO_NEXT_RELATIONSHIP.intValue() ? 0 : (secondNextRel & 0x1_0000_0000L) >> 27; + + long nextProp = record.getNextProp(); + long nextPropMod = nextProp == Record.NO_NEXT_PROPERTY.intValue() ? 0 : (nextProp & 0x3_0000_0000L) >> 26; + + // [ , x] first node higher order bits + // [ , x ] second node high order bits + // [ , x ] first prev high order bits + // [ ,x ] first next high order bits + // [ x, ] second prev high order bits + // [ x , ] second next high order bits + // [xx , ] next prop high order bits + short modifiers = (short) (firstNodeMod | secondNodeMod | firstPrevRelMod | firstNextRelMod | + secondPrevRelMod | secondNextRelMod | nextPropMod); + + cursor.putByte( (byte)modifiers ); + cursor.putInt( (int) firstNode ); + cursor.putInt( (int) secondNode ); + cursor.putInt( (int) firstPrevRel ); + cursor.putInt( (int) firstNextRel ); + cursor.putInt( (int) secondPrevRel ); + cursor.putInt( (int) secondNextRel ); + cursor.putInt( (int) nextProp ); } - encode( cursor, getSecondPrevReference( record, recordId ) ); - if ( record.getSecondNextRel() != NULL ) + else { - encode( cursor, toRelative( record.getSecondNextRel(), recordId ) ); + encode( cursor, record.getNextProp(), NULL ); + encode( cursor, record.getFirstNode() ); + encode( cursor, record.getSecondNode() ); + + encode( cursor, getFirstPrevReference( record, recordId ) ); + if ( record.getFirstNextRel() != NULL ) + { + encode( cursor, toRelative( record.getFirstNextRel(), recordId ) ); + } + encode( cursor, getSecondPrevReference( record, recordId ) ); + if ( record.getSecondNextRel() != NULL ) + { + encode( cursor, toRelative( record.getSecondNextRel(), recordId ) ); + } } } + @Override + protected boolean canUseFixedReferences( RelationshipRecord record ) + { + return ((record.getFirstNode() & 0xFFFF_FFFE_0000_0000L) == 0) && + ((record.getSecondNode() & 0xFFFF_FFFE_0000_0000L) == 0) && + ((record.getFirstPrevRel() == NULL) || + ((record.getFirstPrevRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getFirstNextRel() == NULL) || + ((record.getFirstNextRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getSecondPrevRel() == NULL) || + ((record.getSecondPrevRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getSecondNextRel() == NULL) || + ((record.getSecondNextRel() & 0xFFFF_FFFE_0000_0000L) == 0)) && + ((record.getNextProp() == NULL) || ((record.getNextProp() & 0xFFFF_FFFC_0000_0000L) == 0)); + } + private long getSecondPrevReference( RelationshipRecord record, long recordId ) { return record.isFirstInSecondChain() ? record.getSecondPrevRel() : diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java index 8a25a6e513279..eaec2a2d7e4ff 100644 --- a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/BaseHighLimitRecordFormatTest.java @@ -139,6 +139,12 @@ protected byte headerBits( MyRecord record ) return 0; } + @Override + protected boolean canUseFixedReferences( MyRecord record ) + { + return false; + } + @Override protected int requiredDataLength( MyRecord record ) { diff --git a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java index ab295ea3e7a19..29f9e038ad15f 100644 --- a/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java +++ b/enterprise/kernel/src/test/java/org/neo4j/kernel/impl/store/format/highlimit/PropertyRecordFormatTest.java @@ -78,7 +78,7 @@ private PropertyRecord createRecord( PropertyRecordFormat format, long recordId record.setInUse( true ); record.setId( recordId ); record.setNextProp( 1L ); - record.setPrevProp( 3L ); + record.setPrevProp( (Integer.MAX_VALUE + 1L) << 3 ); return record; } }