From ca273745582a1215f5508a06d0a00f48f06d7d4a Mon Sep 17 00:00:00 2001 From: Mattias Persson Date: Tue, 29 Mar 2016 14:47:21 +0200 Subject: [PATCH] NodeRelationshipCache has 6B ID support since new enterprise format requires it. For this a new ByteArray had to be implemented. ByteArray is just like IntArray/LongArray an abstraction of a primitive array which can grow dynamically and live partly on- as well as off-heap, where ever there's available space. A semi-internal accessor for getting to the most "raw" array instance for a particular ID and making all calls about that ID on that instance has been added as well (NumberArray#at(index)). This makes performance better and so the net performance difference is still positive compared to using a LongArray. --- .../batchimport/cache/BaseNumberArray.java | 56 ++++ .../impl/batchimport/cache/ByteArray.java | 57 +++++ .../batchimport/cache/ChunkedNumberArray.java | 91 ------- .../batchimport/cache/DynamicByteArray.java | 144 +++++++++++ .../batchimport/cache/DynamicIntArray.java | 10 +- .../batchimport/cache/DynamicLongArray.java | 10 +- .../batchimport/cache/DynamicNumberArray.java | 67 ++++- .../impl/batchimport/cache/HeapByteArray.java | 152 +++++++++++ .../impl/batchimport/cache/HeapIntArray.java | 12 +- .../impl/batchimport/cache/HeapLongArray.java | 12 +- .../batchimport/cache/HeapNumberArray.java | 15 +- .../impl/batchimport/cache/IntArray.java | 2 +- .../impl/batchimport/cache/LongArray.java | 2 +- .../cache/NodeRelationshipCache.java | 239 ++++++++++-------- .../impl/batchimport/cache/NumberArray.java | 16 +- .../batchimport/cache/NumberArrayFactory.java | 149 +++++++++-- .../batchimport/cache/OffHeapByteArray.java | 171 +++++++++++++ .../batchimport/cache/OffHeapIntArray.java | 10 +- .../batchimport/cache/OffHeapLongArray.java | 10 +- .../batchimport/cache/OffHeapNumberArray.java | 43 +--- .../cache/OffHeapRegularNumberArray.java | 62 +++++ .../impl/batchimport/cache/ByteArrayTest.java | 66 +++++ .../cache/NodeRelationshipCacheTest.java | 176 ++++++++++--- .../cache/NumberArrayFactoryTest.java | 10 +- .../batchimport/cache/NumberArrayTest.java | 156 ++++++++++++ 25 files changed, 1386 insertions(+), 352 deletions(-) create mode 100644 community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/BaseNumberArray.java create mode 100644 community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArray.java delete mode 100644 community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ChunkedNumberArray.java create mode 100644 community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicByteArray.java create mode 100644 community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapByteArray.java create mode 100644 community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java create mode 100644 community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapRegularNumberArray.java create mode 100644 community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArrayTest.java create mode 100644 community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayTest.java diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/BaseNumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/BaseNumberArray.java new file mode 100644 index 0000000000000..da7af82d6a58f --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/BaseNumberArray.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +/** + * Contains basic functionality of fixed size number arrays. + */ +abstract class BaseNumberArray> implements NumberArray +{ + protected final int itemSize; + private final long base; + + /** + * @param itemSize byte size of each item in this array. + * @param base base index to rebase all indexes in accessor methods off of. See {@link #at(long)}. + */ + protected BaseNumberArray( int itemSize, long base ) + { + this.itemSize = itemSize; + this.base = base; + } + + @SuppressWarnings( "unchecked" ) + @Override + public N at( long index ) + { + return (N)this; + } + + /** + * Utility for rebasing an external index to internal index. + * @param index external index. + * @return index into internal data structure. + */ + protected long rebase( long index ) + { + return index - base; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArray.java new file mode 100644 index 0000000000000..c3439acb16c05 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArray.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +/** + * Abstraction of a {@code byte[]} so that different implementations can be plugged in, for example + * off-heap, dynamically growing, or other implementations. This interface is slightly different than + * {@link IntArray} and {@link LongArray} in that one item in the array isn't necessarily just a byte, + * instead item size can be set to any number and the bytes in an item can be read and written as other + * number representations like {@link #setInt(long, int, int) int} or {@link #setLong(long, int, long) long}, + * even a special {@link #set6BLong(long, int, long) 6B long}. More can easily be added on demand. + * + * @see NumberArrayFactory + */ +public interface ByteArray extends NumberArray +{ + void get( long index, byte[] into ); + + byte getByte( long index, int offset ); + + short getShort( long index, int offset ); + + int getInt( long index, int offset ); + + long get6BLong( long index, int offset ); + + long getLong( long index, int offset ); + + void set( long index, byte[] value ); + + void setByte( long index, int offset, byte value ); + + void setShort( long index, int offset, short value ); + + void setInt( long index, int offset, int value ); + + void set6BLong( long index, int offset, long value ); + + void setLong( long index, int offset, long value ); +} diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ChunkedNumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ChunkedNumberArray.java deleted file mode 100644 index d54fdfbe7d400..0000000000000 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/ChunkedNumberArray.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2002-2016 "Neo Technology," - * Network Engine for Objects in Lund AB [http://neotechnology.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package org.neo4j.unsafe.impl.batchimport.cache; - -/** - * A {@link NumberArray} base built up out of smaller chunks. - */ -abstract class ChunkedNumberArray implements NumberArray -{ - protected final long chunkSize; - protected NumberArray[] chunks; - - ChunkedNumberArray( long chunkSize ) - { - this.chunkSize = chunkSize; - } - - @Override - public long length() - { - return chunks.length * chunkSize; - } - - @Override - public void clear() - { - for ( NumberArray chunk : chunks ) - { - chunk.clear(); - } - } - - @Override - public void acceptMemoryStatsVisitor( MemoryStatsVisitor visitor ) - { - for ( NumberArray chunk : chunks ) - { - chunk.acceptMemoryStatsVisitor( visitor ); - } - } - - @SuppressWarnings( "unchecked" ) - protected N chunkAt( long index ) - { - int chunkIndex = chunkIndex( index ); - return (N) chunks[chunkIndex]; - } - - @SuppressWarnings( "unchecked" ) - protected N chunkOrNullAt( long index ) - { - int chunkIndex = chunkIndex( index ); - return chunkIndex < chunks.length ? (N) chunks[chunkIndex] : null; - } - - protected int chunkIndex( long index ) - { - return (int) (index/chunkSize); - } - - protected long index( long index ) - { - return index % chunkSize; - } - - @Override - public void close() - { - for ( NumberArray chunk : chunks ) - { - chunk.close(); - } - } -} diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicByteArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicByteArray.java new file mode 100644 index 0000000000000..790d3a4580dcd --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicByteArray.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +import java.nio.ByteBuffer; + +import static org.neo4j.unsafe.impl.batchimport.cache.HeapByteArray.get6BLongFromByteBuffer; + +public class DynamicByteArray extends DynamicNumberArray implements ByteArray +{ + private final byte[] defaultValue; + private final ByteBuffer defaultValueConvenienceBuffer; + + public DynamicByteArray( NumberArrayFactory factory, long chunkSize, byte[] defaultValue ) + { + super( factory, chunkSize, new ByteArray[0] ); + this.defaultValue = defaultValue; + this.defaultValueConvenienceBuffer = ByteBuffer.wrap( defaultValue ); + } + + @Override + public void swap( long fromIndex, long toIndex, int numberOfEntries ) + { + // Let's just do this the stupid way. There's room for optimization here + byte[] intermediary = defaultValue.clone(); + byte[] transport = defaultValue.clone(); + for ( int i = 0; i < numberOfEntries; i++ ) + { + get( fromIndex+i, intermediary ); + get( toIndex+i, transport ); + set( fromIndex+i, transport ); + set( toIndex+i, intermediary ); + } + } + + @Override + public void get( long index, byte[] into ) + { + ByteArray chunk = chunkOrNullAt( index ); + if ( chunk != null ) + { + chunk.get( index, into ); + } + else + { + System.arraycopy( defaultValue, 0, into, 0, defaultValue.length ); + } + } + + @Override + public byte getByte( long index, int offset ) + { + ByteArray chunk = chunkOrNullAt( index ); + return chunk != null ? chunk.getByte( index, offset ) : defaultValueConvenienceBuffer.get( offset ); + } + + @Override + public short getShort( long index, int offset ) + { + ByteArray chunk = chunkOrNullAt( index ); + return chunk != null ? chunk.getShort( index, offset ) : defaultValueConvenienceBuffer.getShort( offset ); + } + + @Override + public int getInt( long index, int offset ) + { + ByteArray chunk = chunkOrNullAt( index ); + return chunk != null ? chunk.getInt( index, offset ) : defaultValueConvenienceBuffer.getInt( offset ); + } + + @Override + public long get6BLong( long index, int offset ) + { + ByteArray chunk = chunkOrNullAt( index ); + return chunk != null ? chunk.get6BLong( index, offset ) : + get6BLongFromByteBuffer( defaultValueConvenienceBuffer, offset ); + } + + @Override + public long getLong( long index, int offset ) + { + ByteArray chunk = chunkOrNullAt( index ); + return chunk != null ? chunk.getLong( index, offset ) : defaultValueConvenienceBuffer.getLong( offset ); + } + + @Override + public void set( long index, byte[] value ) + { + at( index ).set( index, value ); + } + + @Override + public void setByte( long index, int offset, byte value ) + { + at( index ).setByte( index, offset, value ); + } + + @Override + public void setShort( long index, int offset, short value ) + { + at( index ).setShort( index, offset, value ); + } + + @Override + public void setInt( long index, int offset, int value ) + { + at( index ).setInt( index, offset, value ); + } + + @Override + public void set6BLong( long index, int offset, long value ) + { + at( index ).set6BLong( index, offset, value ); + } + + @Override + public void setLong( long index, int offset, long value ) + { + at( index ).setLong( index, offset, value ); + } + + @Override + protected ByteArray addChunk( long chunkSize, long base ) + { + return factory.newByteArray( chunkSize, defaultValue, base ); + } +} diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicIntArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicIntArray.java index 40c6ced89a443..92205fbc56a5f 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicIntArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicIntArray.java @@ -31,7 +31,7 @@ public class DynamicIntArray extends DynamicNumberArray implements Int public DynamicIntArray( NumberArrayFactory factory, long chunkSize, int defaultValue ) { - super( factory, chunkSize ); + super( factory, chunkSize, new IntArray[0] ); this.defaultValue = defaultValue; } @@ -39,13 +39,13 @@ public DynamicIntArray( NumberArrayFactory factory, long chunkSize, int defaultV public int get( long index ) { IntArray chunk = chunkOrNullAt( index ); - return chunk != null ? chunk.get( index( index ) ) : defaultValue; + return chunk != null ? chunk.get( index ) : defaultValue; } @Override public void set( long index, int value ) { - ensureChunkAt( index ).set( index( index ), value ); + at( index ).set( index, value ); } @Override @@ -61,8 +61,8 @@ public void swap( long fromIndex, long toIndex, int numberOfEntries ) } @Override - protected IntArray addChunk( long chunkSize ) + protected IntArray addChunk( long chunkSize, long base ) { - return factory.newIntArray( chunkSize, defaultValue ); + return factory.newIntArray( chunkSize, defaultValue, base ); } } diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicLongArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicLongArray.java index d5d477c7b4593..91c7d1d9c1b12 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicLongArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicLongArray.java @@ -31,7 +31,7 @@ public class DynamicLongArray extends DynamicNumberArray implements L public DynamicLongArray( NumberArrayFactory factory, long chunkSize, long defaultValue ) { - super( factory, chunkSize ); + super( factory, chunkSize, new LongArray[0] ); this.defaultValue = defaultValue; } @@ -39,13 +39,13 @@ public DynamicLongArray( NumberArrayFactory factory, long chunkSize, long defaul public long get( long index ) { LongArray chunk = chunkOrNullAt( index ); - return chunk != null ? chunk.get( index( index ) ) : defaultValue; + return chunk != null ? chunk.get( index ) : defaultValue; } @Override public void set( long index, long value ) { - ensureChunkAt( index ).set( index( index ), value ); + at( index ).set( index, value ); } @Override @@ -61,8 +61,8 @@ public void swap( long fromIndex, long toIndex, int numberOfEntries ) } @Override - protected LongArray addChunk( long chunkSize ) + protected LongArray addChunk( long chunkSize, long base ) { - return factory.newLongArray( chunkSize, defaultValue ); + return factory.newLongArray( chunkSize, defaultValue, base ); } } diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicNumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicNumberArray.java index f8e4c700a4887..9e5c5f8604129 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicNumberArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/DynamicNumberArray.java @@ -28,24 +28,64 @@ * @see NumberArrayFactory#newDynamicLongArray(long, long) * @see NumberArrayFactory#newDynamicIntArray(long, int) */ -abstract class DynamicNumberArray extends ChunkedNumberArray +abstract class DynamicNumberArray> implements NumberArray { protected final NumberArrayFactory factory; + protected final long chunkSize; + protected N[] chunks; - DynamicNumberArray( NumberArrayFactory factory, long chunkSize ) + DynamicNumberArray( NumberArrayFactory factory, long chunkSize, N[] initialChunks ) { - super( chunkSize ); this.factory = factory; - this.chunks = new NumberArray[0]; + this.chunkSize = chunkSize; + this.chunks = initialChunks; } - protected N ensureChunkAt( long index ) + @Override + public long length() + { + return chunks.length * chunkSize; + } + + @Override + public void clear() + { + for ( N chunk : chunks ) + { + chunk.clear(); + } + } + + @Override + public void acceptMemoryStatsVisitor( MemoryStatsVisitor visitor ) + { + for ( N chunk : chunks ) + { + chunk.acceptMemoryStatsVisitor( visitor ); + } + } + + protected N chunkOrNullAt( long index ) + { + int chunkIndex = chunkIndex( index ); + return chunkIndex < chunks.length ? (N) chunks[chunkIndex] : null; + } + + protected int chunkIndex( long index ) + { + return (int) (index/chunkSize); + } + + @Override + public N at( long index ) { if ( index >= length() ) { synchronizedAddChunk( index ); } - return super.chunkAt( index ); + + int chunkIndex = chunkIndex( index ); + return chunks[chunkIndex]; } private void synchronizedAddChunk( long index ) @@ -54,15 +94,24 @@ private void synchronizedAddChunk( long index ) { if ( index >= length() ) { - NumberArray[] newChunks = Arrays.copyOf( chunks, chunkIndex( index )+1 ); + N[] newChunks = Arrays.copyOf( chunks, chunkIndex( index )+1 ); for ( int i = chunks.length; i < newChunks.length; i++ ) { - newChunks[i] = addChunk( chunkSize ); + newChunks[i] = addChunk( chunkSize, chunkSize * i ); } chunks = newChunks; } } } - protected abstract NumberArray addChunk( long chunkSize ); + protected abstract N addChunk( long chunkSize, long base ); + + @Override + public void close() + { + for ( N chunk : chunks ) + { + chunk.close(); + } + } } diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapByteArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapByteArray.java new file mode 100644 index 0000000000000..4f547b0383067 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapByteArray.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +import java.nio.ByteBuffer; + +import static java.lang.Math.toIntExact; + +public class HeapByteArray extends HeapNumberArray implements ByteArray +{ + private final int length; + private final byte[] array; + private final ByteBuffer buffer; + private final byte[] defaultValue; + + public HeapByteArray( int length, byte[] defaultValue, int base ) + { + super( defaultValue.length, base ); + this.length = length; + this.defaultValue = defaultValue; + this.array = new byte[itemSize * length]; + this.buffer = ByteBuffer.wrap( array ); + clear(); + } + + @Override + public long length() + { + return length; + } + + @Override + public void swap( long fromIndex, long toIndex, int numberOfEntries ) + { + byte[] intermediary = new byte[numberOfEntries * itemSize]; + System.arraycopy( array, index( toIndex, 0 ), intermediary, 0, intermediary.length ); + System.arraycopy( array, index( fromIndex, 0 ), array, index( toIndex, 0 ), intermediary.length ); + System.arraycopy( intermediary, 0, array, index( fromIndex, 0 ), intermediary.length ); + } + + @Override + public void clear() + { + for ( int i = 0; i < length; i++ ) + { + System.arraycopy( defaultValue, 0, array, i * itemSize, itemSize ); + } + } + + @Override + public void get( long index, byte[] into ) + { + System.arraycopy( array, index( index, 0 ), into, 0, itemSize ); + } + + @Override + public byte getByte( long index, int offset ) + { + return buffer.get( index( index, offset ) ); + } + + @Override + public short getShort( long index, int offset ) + { + return buffer.getShort( index( index, offset ) ); + } + + @Override + public int getInt( long index, int offset ) + { + return buffer.getInt( index( index, offset ) ); + } + + @Override + public long get6BLong( long index, int offset ) + { + return get6BLongFromByteBuffer( buffer, index( index, offset ) ); + } + + protected static long get6BLongFromByteBuffer( ByteBuffer buffer, int startOffset ) + { + long low4b = buffer.getInt( startOffset ) & 0xFFFFFFFFL; + long high2b = buffer.getShort( startOffset + Integer.BYTES ); + return low4b | (high2b << 32); + } + + @Override + public long getLong( long index, int offset ) + { + return buffer.getLong( index( index, offset ) ); + } + + @Override + public void set( long index, byte[] value ) + { + System.arraycopy( value, 0, array, index( index, 0 ), itemSize ); + } + + @Override + public void setByte( long index, int offset, byte value ) + { + buffer.put( index( index, offset ), value ); + } + + @Override + public void setShort( long index, int offset, short value ) + { + buffer.putShort( index( index, offset ), value ); + } + + @Override + public void setInt( long index, int offset, int value ) + { + buffer.putInt( index( index, offset ), value ); + } + + @Override + public void set6BLong( long index, int offset, long value ) + { + int absIndex = index( index, offset ); + buffer.putInt( absIndex, (int) value ); + buffer.putShort( absIndex + Integer.BYTES, (short) (value >>> 32) ); + } + + @Override + public void setLong( long index, int offset, long value ) + { + buffer.putLong( index( index, offset ), value ); + } + + private int index( long index, int offset ) + { + return toIntExact( (rebase( index ) * itemSize) + offset ); + } +} diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapIntArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapIntArray.java index e723fbd0bda2a..80c89723a3796 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapIntArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapIntArray.java @@ -21,19 +21,17 @@ import java.util.Arrays; -import static org.neo4j.unsafe.impl.batchimport.Utils.safeCastLongToInt; - /** * A {@code long[]} on heap, abstracted into a {@link IntArray}. */ -public class HeapIntArray extends HeapNumberArray implements IntArray +public class HeapIntArray extends HeapNumberArray implements IntArray { private final int[] array; private final int defaultValue; - public HeapIntArray( int length, int defaultValue ) + public HeapIntArray( int length, int defaultValue, int base ) { - super( 4 ); + super( 4, base ); this.defaultValue = defaultValue; this.array = new int[length]; clear(); @@ -48,13 +46,13 @@ public long length() @Override public int get( long index ) { - return array[safeCastLongToInt( index )]; + return array[index( index )]; } @Override public void set( long index, int value ) { - array[safeCastLongToInt( index )] = value; + array[index( index )] = value; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapLongArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapLongArray.java index a53e69218aa4e..1dba540816271 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapLongArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapLongArray.java @@ -21,19 +21,17 @@ import java.util.Arrays; -import static org.neo4j.unsafe.impl.batchimport.Utils.safeCastLongToInt; - /** * A {@code long[]} on heap, abstracted into a {@link LongArray}. */ -public class HeapLongArray extends HeapNumberArray implements LongArray +public class HeapLongArray extends HeapNumberArray implements LongArray { private final long[] array; private final long defaultValue; - public HeapLongArray( int length, long defaultValue ) + public HeapLongArray( int length, long defaultValue, int base ) { - super( 8 ); + super( 8, base ); this.defaultValue = defaultValue; this.array = new long[length]; clear(); @@ -48,13 +46,13 @@ public long length() @Override public long get( long index ) { - return array[safeCastLongToInt( index )]; + return array[index( index )]; } @Override public void set( long index, long value ) { - array[safeCastLongToInt( index )] = value; + array[index( index )] = value; } @Override diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapNumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapNumberArray.java index 4e0c0081db7a5..ee32d77329755 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapNumberArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/HeapNumberArray.java @@ -19,16 +19,16 @@ */ package org.neo4j.unsafe.impl.batchimport.cache; +import static org.neo4j.unsafe.impl.batchimport.Utils.safeCastLongToInt; + /** * Base class for common functionality for any {@link NumberArray} where the data lives inside heap. */ -abstract class HeapNumberArray implements NumberArray +abstract class HeapNumberArray> extends BaseNumberArray { - private final int itemSize; - - protected HeapNumberArray( int itemSize ) + protected HeapNumberArray( int itemSize, long base ) { - this.itemSize = itemSize; + super( itemSize, base ); } @Override @@ -41,4 +41,9 @@ public void acceptMemoryStatsVisitor( MemoryStatsVisitor visitor ) public void close() { // Nothing to close } + + protected int index( long index ) + { + return safeCastLongToInt( rebase( index ) ); + } } diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/IntArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/IntArray.java index ca5c7720bdad5..b41c2fe5cbded 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/IntArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/IntArray.java @@ -25,7 +25,7 @@ * * @see NumberArrayFactory */ -public interface IntArray extends NumberArray +public interface IntArray extends NumberArray { int get( long index ); diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/LongArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/LongArray.java index ce06a8447e9ca..30414c1385a9f 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/LongArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/LongArray.java @@ -25,7 +25,7 @@ * * @see NumberArrayFactory */ -public interface LongArray extends NumberArray +public interface LongArray extends NumberArray { long get( long index ); diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCache.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCache.java index b0cd3eb28b20c..ca153da4dd04b 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCache.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCache.java @@ -30,23 +30,30 @@ public class NodeRelationshipCache implements MemoryStatsVisitor.Visitable { private static final long EMPTY = -1; + private static final byte[] DEFAULT_VALUE = new byte[10]; + private static final long MAX_RELATIONSHIP_ID = (1L << 48/*6B*/) - 2/*reserving -1 as legal default value*/; + static + { + // This looks odd, but we're using the array itself to create a default byte[] for another + ByteArray array = NumberArrayFactory.HEAP.newByteArray( 1, DEFAULT_VALUE.clone() ); + array.set6BLong( 0, 0, EMPTY ); + array.setInt( 0, 6, 0 ); + array.get( 0, DEFAULT_VALUE ); + } - private final LongArray array; + private final ByteArray array; private final int denseNodeThreshold; private final RelGroupCache relGroupCache; - private final long base; public NodeRelationshipCache( NumberArrayFactory arrayFactory, int denseNodeThreshold ) { - this( arrayFactory, denseNodeThreshold, 0 ); + this( arrayFactory, denseNodeThreshold, 1_000_000, 0 ); } - NodeRelationshipCache( NumberArrayFactory arrayFactory, int denseNodeThreshold, long base ) + NodeRelationshipCache( NumberArrayFactory arrayFactory, int denseNodeThreshold, int chunkSize, long base ) { - int chunkSize = 1_000_000; - this.array = arrayFactory.newDynamicLongArray( chunkSize, IdFieldManipulator.emptyField() ); + this.array = arrayFactory.newDynamicByteArray( chunkSize, DEFAULT_VALUE ); this.denseNodeThreshold = denseNodeThreshold; - this.base = base; this.relGroupCache = new RelGroupCache( arrayFactory, chunkSize, base ); } @@ -57,30 +64,35 @@ public NodeRelationshipCache( NumberArrayFactory arrayFactory, int denseNodeThre */ public int incrementCount( long nodeId ) { - long field = array.get( nodeId ); - field = IdFieldManipulator.changeCount( field, 1 ); - array.set( nodeId, field ); - return IdFieldManipulator.getCount( field ); + ByteArray array = this.array.at( nodeId ); + int count = array.getInt( nodeId, 6 ) + 1; + array.setInt( nodeId, 6, count ); + return count; } public boolean isDense( long nodeId ) { - return fieldIsDense( array.get( nodeId ) ); + return isDense( array, nodeId ); } - private boolean fieldIsDense( long field ) + private boolean isDense( ByteArray array, long nodeId ) { if ( denseNodeThreshold == EMPTY ) { // We haven't initialized the rel group cache yet return false; } - return IdFieldManipulator.getCount( field ) >= denseNodeThreshold; + return array.getInt( nodeId, 6 ) >= denseNodeThreshold; } public long getAndPutRelationship( long nodeId, int type, Direction direction, long firstRelId, boolean incrementCount ) { + if ( firstRelId > MAX_RELATIONSHIP_ID ) + { + throw new IllegalArgumentException( "Illegal relationship id, max is " + MAX_RELATIONSHIP_ID ); + } + /* * OK so the story about counting goes: there's an initial pass for counting number of relationships * per node, globally, not per type/direction. After that the relationship group cache is initialized @@ -88,40 +100,44 @@ public long getAndPutRelationship( long nodeId, int type, Direction direction, l * not increment the global count, but it should increment the type/direction counts. */ - long field = array.get( nodeId ); - long existingId = IdFieldManipulator.getId( field ); - if ( fieldIsDense( field ) ) + ByteArray array = this.array.at( nodeId ); + long existingId = all48Bits( array, nodeId, 0 ); + if ( isDense( array, nodeId ) ) { if ( existingId == EMPTY ) { existingId = relGroupCache.allocate( type, direction, firstRelId, incrementCount ); - field = IdFieldManipulator.setId( field, existingId ); - array.set( nodeId, field ); + array.set6BLong( nodeId, 0, existingId ); return EMPTY; } return relGroupCache.putRelationship( existingId, type, direction, firstRelId, incrementCount ); } - field = IdFieldManipulator.setId( field, firstRelId ); // Don't increment count for sparse node since that has already been done in a previous pass - array.set( nodeId, field ); + array.set6BLong( nodeId, 0, firstRelId ); return existingId; } + private static long all48Bits( ByteArray array, long index, int offset ) + { + long raw = array.get6BLong( index, offset ); + return raw == -1L ? raw : raw & 0xFFFFFFFFFFFFL; + } + /** * Used when setting node nextRel fields. Gets the first relationship for this node, * or the first relationship group id (where it it first visits all the groups before returning the first one). */ public long getFirstRel( long nodeId, GroupVisitor visitor ) { - long field = array.get( nodeId ); - if ( fieldIsDense( field ) ) + ByteArray array = this.array.at( nodeId ); + long id = all48Bits( array, nodeId, 0 ); + if ( isDense( array, nodeId ) ) { // Indirection into rel group cache - long relGroupIndex = IdFieldManipulator.getId( field ); - return relGroupCache.visitGroups( nodeId, relGroupIndex, visitor ); + return relGroupCache.visitGroups( nodeId, id, visitor ); } - return IdFieldManipulator.getId( field ); + return id; } public void clearRelationships() @@ -129,11 +145,9 @@ public void clearRelationships() long length = array.length(); for ( long i = 0; i < length; i++ ) { - long field = array.get( i ); - if ( !fieldIsDense( field ) ) + if ( !isDense( i ) ) { - field = IdFieldManipulator.cleanId( field ); - array.set( i, field ); + array.set6BLong( i, 0, -1 ); } } relGroupCache.clearRelationships(); @@ -141,23 +155,14 @@ public void clearRelationships() public int getCount( long nodeId, int type, Direction direction ) { - long field = array.get( nodeId ); - if ( fieldIsDense( field ) ) + ByteArray array = this.array.at( nodeId ); + if ( isDense( array, nodeId ) ) { // Indirection into rel group cache - long relGroupIndex = IdFieldManipulator.getId( field ); - if ( relGroupIndex == EMPTY ) - { - return 0; - } - relGroupIndex = relGroupCache.findGroupIndexForType( relGroupIndex, type ); - if ( relGroupIndex == EMPTY ) - { - return 0; - } - field = relGroupCache.getField( relGroupIndex, relGroupCache.directionIndex( direction ) ); + long id = all48Bits( array, nodeId, 0 ); + return id == EMPTY ? 0 : relGroupCache.getCount( id, type, direction ); } - return IdFieldManipulator.getCount( field ); + return array.getInt( nodeId, 6 ); } public interface GroupVisitor @@ -180,43 +185,63 @@ public long visit( long nodeId, int type, long next, long out, long in, long loo private static class RelGroupCache implements AutoCloseable, MemoryStatsVisitor.Visitable { - private static final int ENTRY_SIZE = 4; + private static final byte[] DEFAULT_VALUE = new byte[6 + 2 + (6 + 4) * Direction.values().length]; + static + { + ByteArray defaultArray = NumberArrayFactory.HEAP.newByteArray( 1, DEFAULT_VALUE.clone() ); + defaultArray.set6BLong( 0, 0, EMPTY ); + defaultArray.setShort( 0, 6, (short) EMPTY ); + for ( int i = 0, offsetBase = 8; i < 3; i++, offsetBase += 10 ) + { + defaultArray.set6BLong( 0, offsetBase, EMPTY ); + defaultArray.setInt( 0, offsetBase + 6, 0 ); + } + defaultArray.get( 0, DEFAULT_VALUE ); + } - private static final int INDEX_NEXT_AND_TYPE = 0; - private static final int INDEX_OUT = 1; - private static final int INDEX_IN = 2; - private static final int INDEX_LOOP = 3; + private static final int NEXT_OFFSET = 0; + private static final int TYPE_OFFSET = 6; // Used for testing high id values. Should always be zero in production private final long base; - private final LongArray array; + private final ByteArray array; private final AtomicLong nextFreeId; RelGroupCache( NumberArrayFactory arrayFactory, long chunkSize, long base ) { this.base = base; assert chunkSize > 0; - this.array = arrayFactory.newDynamicLongArray( chunkSize, -1 ); + // We can use this array to have "entries" accommodating one entire group, e.g: + // - next + // - type + // - out + // - out degree + // - in + // - in degree + // - loop + // - loop degree + this.array = arrayFactory.newDynamicByteArray( chunkSize, DEFAULT_VALUE ); this.nextFreeId = new AtomicLong( base ); } + public int getCount( long id, int type, Direction direction ) + { + id = findGroupIndexForType( id, type ); + return id == EMPTY ? 0 : array.getInt( rebase( id ), countOffset( direction ) ); + } + private void clearRelationships() { - long length = array.length() / ENTRY_SIZE; + long length = array.length(); for ( long i = 0; i < length; i++ ) { - clearRelationshipId( i, INDEX_OUT ); - clearRelationshipId( i, INDEX_IN ); - clearRelationshipId( i, INDEX_LOOP ); + ByteArray array = this.array.at( i ); + array.set6BLong( i, directionOffset( Direction.OUTGOING ), EMPTY ); + array.set6BLong( i, directionOffset( Direction.INCOMING ), EMPTY ); + array.set6BLong( i, directionOffset( Direction.BOTH ), EMPTY ); } } - private void clearRelationshipId( long relGroupIndex, int fieldIndex ) - { - long index = index( relGroupIndex, fieldIndex ); - array.set( rebase( index ), IdFieldManipulator.cleanId( array.get( index ) ) ); - } - /** * Compensate for test value of index (to avoid allocating all your RAM) */ @@ -230,12 +255,10 @@ private long nextFreeId() return nextFreeId.getAndIncrement(); } - private void initializeGroup( long relGroupIndex, int type ) + private void initializeGroup( ByteArray array, long relGroupIndex, int type ) { - setField( relGroupIndex, INDEX_NEXT_AND_TYPE, NextFieldManipulator.initialFieldWithType( type ) ); - setField( relGroupIndex, INDEX_OUT, IdFieldManipulator.emptyField() ); - setField( relGroupIndex, INDEX_IN, IdFieldManipulator.emptyField() ); - setField( relGroupIndex, INDEX_LOOP, IdFieldManipulator.emptyField() ); + array.setShort( rebase( relGroupIndex ), TYPE_OFFSET, (short) type ); + // All other values are set to defaults automatically } private long visitGroups( long nodeId, long relGroupIndex, GroupVisitor visitor ) @@ -244,11 +267,13 @@ private long visitGroups( long nodeId, long relGroupIndex, GroupVisitor visitor long first = -1; while ( currentIndex != EMPTY ) { - int type = NextFieldManipulator.getType( getField( currentIndex, INDEX_NEXT_AND_TYPE ) ); - long out = IdFieldManipulator.getId( getField( currentIndex, INDEX_OUT ) ); - long in = IdFieldManipulator.getId( getField( currentIndex, INDEX_IN ) ); - long loop = IdFieldManipulator.getId( getField( currentIndex, INDEX_LOOP ) ); - long next = NextFieldManipulator.getNext( getField( currentIndex, INDEX_NEXT_AND_TYPE ) ); + long index = rebase( currentIndex ); + ByteArray array = this.array.at( index ); + int type = array.getShort( index, TYPE_OFFSET ); + long out = all48Bits( array, index, directionOffset( Direction.OUTGOING ) ); + long in = all48Bits( array, index, directionOffset( Direction.INCOMING ) ); + long loop = all48Bits( array, index, directionOffset( Direction.BOTH ) ); + long next = all48Bits( array, index, NEXT_OFFSET ); long id = visitor.visit( nodeId, type, next, out, in, loop ); if ( first == -1 ) { // This is the one we return @@ -260,45 +285,37 @@ private long visitGroups( long nodeId, long relGroupIndex, GroupVisitor visitor return first; } - private void setField( long relGroupIndex, int index, long field ) - { - array.set( index( relGroupIndex, index ), field ); - } - - private long getField( long relGroupIndex, int index ) - { - return array.get( index( relGroupIndex, index ) ); - } - - private int directionIndex( Direction direction ) + private int directionOffset( Direction direction ) { - return direction.ordinal()+1; + return 8 + (direction.ordinal() * 10); } - private long index( long relGroupIndex, int fieldIndex ) + private int countOffset( Direction direction ) { - return rebase( relGroupIndex ) * ENTRY_SIZE + fieldIndex; + return directionOffset( direction ) + 6; } public long allocate( int type, Direction direction, long relId, boolean incrementCount ) { - long logicalPosition = nextFreeId(); - initializeGroup( logicalPosition, type ); - putRelField( logicalPosition, direction, relId, incrementCount ); - return logicalPosition; + long index = nextFreeId(); + ByteArray array = this.array.at( rebase( index ) ); + initializeGroup( array, index, type ); + putRelField( array, index, direction, relId, incrementCount ); + return index; } - private long putRelField( long relGroupIndex, Direction direction, long relId, boolean increment ) + private long putRelField( ByteArray array, long relGroupIndex, Direction direction, + long relId, boolean increment ) { - int directionIndex = directionIndex( direction ); - long field = getField( relGroupIndex, directionIndex ); - long previousId = IdFieldManipulator.getId( field ); - field = IdFieldManipulator.setId( field, relId ); + long index = rebase( relGroupIndex ); + int directionOffset = directionOffset( direction ); + long previousId = all48Bits( array, index, directionOffset ); + array.set6BLong( index, directionOffset, relId ); if ( increment ) { - field = IdFieldManipulator.changeCount( field, 1 ); + int countOffset = countOffset( direction ); + array.setInt( index, countOffset, array.getInt( index, countOffset ) + 1 ); } - setField( relGroupIndex, directionIndex, field ); return previousId; } @@ -309,47 +326,48 @@ public long putRelationship( long relGroupIndex, int type, Direction direction, long previousIndex = EMPTY; while ( currentIndex != EMPTY ) { - long foundType = NextFieldManipulator.getType( getField( currentIndex, INDEX_NEXT_AND_TYPE ) ); + long currentIndexRebased = rebase( currentIndex ); + ByteArray array = this.array.at( currentIndexRebased ); + long foundType = array.getShort( currentIndexRebased, TYPE_OFFSET ); if ( foundType == type ) { // Found it - return putRelField( currentIndex, direction, relId, trueForIncrement ); + return putRelField( array, currentIndex, direction, relId, trueForIncrement ); } else if ( foundType > type ) { // We came too far, create room for it break; } previousIndex = currentIndex; - currentIndex = NextFieldManipulator.getNext( getField( currentIndex, INDEX_NEXT_AND_TYPE ) ); + currentIndex = all48Bits( array, currentIndexRebased, NEXT_OFFSET ); } long newIndex = nextFreeId(); if ( previousIndex == EMPTY ) { // We are at the start - array.swap( index( currentIndex, 0 ), index( newIndex, 0 ), ENTRY_SIZE ); + array.swap( rebase( currentIndex ), rebase( newIndex ), 1 ); long swap = newIndex; newIndex = currentIndex; currentIndex = swap; } - initializeGroup( newIndex, type ); + ByteArray array = this.array.at( rebase( newIndex ) ); + initializeGroup( array, newIndex, type ); if ( currentIndex != EMPTY ) { // We are NOT at the end - setNextField( newIndex, currentIndex ); + setNextField( array, newIndex, currentIndex ); } if ( previousIndex != EMPTY ) { // We are NOT at the start - setNextField( previousIndex, newIndex ); + setNextField( this.array, previousIndex, newIndex ); } - return putRelField( newIndex, direction, relId, trueForIncrement ); + return putRelField( array, newIndex, direction, relId, trueForIncrement ); } - private void setNextField( long relGroupIndex, long next ) + private void setNextField( ByteArray array, long relGroupIndex, long next ) { - long field = getField( relGroupIndex, INDEX_NEXT_AND_TYPE ); - field = NextFieldManipulator.setNext( field, next ); - setField( relGroupIndex, INDEX_NEXT_AND_TYPE, field ); + array.set6BLong( rebase( relGroupIndex ), NEXT_OFFSET, next ); } private long findGroupIndexForType( long relGroupIndex, int type ) @@ -357,7 +375,8 @@ private long findGroupIndexForType( long relGroupIndex, int type ) long currentIndex = relGroupIndex; while ( currentIndex != EMPTY ) { - int foundType = NextFieldManipulator.getType( getField( currentIndex, INDEX_NEXT_AND_TYPE ) ); + long index = rebase( currentIndex ); + int foundType = array.getShort( index, TYPE_OFFSET ); if ( foundType == type ) { // Found it return currentIndex; @@ -366,7 +385,7 @@ else if ( foundType > type ) { // We came too far, create room for it break; } - currentIndex = NextFieldManipulator.getNext( getField( currentIndex, INDEX_NEXT_AND_TYPE ) ); + currentIndex = all48Bits( array, index, 0 ); } return EMPTY; } diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArray.java index 879a40c92489d..b07a0a20b1523 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArray.java @@ -24,7 +24,7 @@ * * @see NumberArrayFactory */ -public interface NumberArray extends MemoryStatsVisitor.Visitable, AutoCloseable +public interface NumberArray> extends MemoryStatsVisitor.Visitable, AutoCloseable { /** * @return length of the array, i.e. the capacity. @@ -51,4 +51,18 @@ public interface NumberArray extends MemoryStatsVisitor.Visitable, AutoCloseable */ @Override void close(); + + /** + * Part of the nature of {@link NumberArray} is that {@link #length()} can be dynamically growing. + * For that to work some implementations (those coming from e.g + * {@link NumberArrayFactory#newDynamicIntArray(long, int)} and such dynamic calls) has an indirection, + * one that is a bit costly when comparing to raw array access. In scenarios where there will be two or + * more access to the same index in the array it will be more efficient to resolve this indirection once + * and return the "raw" array for that given index so that it can be used directly in multiple calls, + * knowing that the returned array holds the given index. + * + * @param index index into the array which the returned array will contain. + * @return array sure to hold the given index. + */ + N at( long index ); } diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactory.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactory.java index ed251c45afa94..a2cd43aa07891 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactory.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactory.java @@ -23,6 +23,7 @@ import org.neo4j.helpers.Exceptions; +import static java.lang.Math.toIntExact; import static java.lang.String.format; import static org.neo4j.helpers.Format.bytes; @@ -40,7 +41,18 @@ public interface NumberArrayFactory * @param defaultValue value which will represent unset values. * @return a fixed size {@link IntArray}. */ - IntArray newIntArray( long length, int defaultValue ); + default IntArray newIntArray( long length, int defaultValue ) + { + return newIntArray( length, defaultValue, 0 ); + } + + /** + * @param length size of the array. + * @param defaultValue value which will represent unset values. + * @param base base index to rebase all requested indexes with. + * @return a fixed size {@link IntArray}. + */ + IntArray newIntArray( long length, int defaultValue, long base ); /** * @param chunkSize the size of each array (number of items). Where new chunks are added when needed. @@ -54,7 +66,18 @@ public interface NumberArrayFactory * @param defaultValue value which will represent unset values. * @return a fixed size {@link LongArray}. */ - LongArray newLongArray( long length, long defaultValue ); + default LongArray newLongArray( long length, long defaultValue ) + { + return newLongArray( length, defaultValue, 0 ); + } + + /** + * @param length size of the array. + * @param defaultValue value which will represent unset values. + * @param base base index to rebase all requested indexes with. + * @return a fixed size {@link LongArray}. + */ + LongArray newLongArray( long length, long defaultValue, long base ); /** * @param chunkSize the size of each array (number of items). Where new chunks are added when needed. @@ -63,6 +86,31 @@ public interface NumberArrayFactory */ LongArray newDynamicLongArray( long chunkSize, long defaultValue ); + /** + * @param length size of the array. + * @param defaultValue value which will represent unset values. + * @return a fixed size {@link ByteArray}. + */ + default ByteArray newByteArray( long length, byte[] defaultValue ) + { + return newByteArray( length, defaultValue, 0 ); + } + + /** + * @param length size of the array. + * @param defaultValue value which will represent unset values. + * @param base base index to rebase all requested indexes with. + * @return a fixed size {@link ByteArray}. + */ + ByteArray newByteArray( long length, byte[] defaultValue, long base ); + + /** + * @param chunkSize the size of each array (number of items). Where new chunks are added when needed. + * @param defaultValue value which will represent unset values. + * @return dynamically growing {@link ByteArray}. + */ + ByteArray newDynamicByteArray( long chunkSize, byte[] defaultValue ); + /** * Implements the dynamic array methods, because they are the same in most implementations. */ @@ -79,6 +127,12 @@ public LongArray newDynamicLongArray( long chunkSize, long defaultValue ) { return new DynamicLongArray( this, chunkSize, defaultValue ); } + + @Override + public ByteArray newDynamicByteArray( long chunkSize, byte[] defaultValue ) + { + return new DynamicByteArray( this, chunkSize, defaultValue ); + } } /** @@ -87,15 +141,21 @@ public LongArray newDynamicLongArray( long chunkSize, long defaultValue ) NumberArrayFactory HEAP = new Adapter() { @Override - public IntArray newIntArray( long length, int defaultValue ) + public IntArray newIntArray( long length, int defaultValue, long base ) { - return new HeapIntArray( safeCastLongToInt( length ), defaultValue ); + return new HeapIntArray( safeCastLongToInt( length ), defaultValue, toIntExact( base ) ); } @Override - public LongArray newLongArray( long length, long defaultValue ) + public LongArray newLongArray( long length, long defaultValue, long base ) { - return new HeapLongArray( safeCastLongToInt( length ), defaultValue ); + return new HeapLongArray( safeCastLongToInt( length ), defaultValue, toIntExact( base ) ); + } + + @Override + public ByteArray newByteArray( long length, byte[] defaultValue, long base ) + { + return new HeapByteArray( safeCastLongToInt( length ), defaultValue, toIntExact( base ) ); } @Override @@ -111,15 +171,21 @@ public String toString() NumberArrayFactory OFF_HEAP = new Adapter() { @Override - public IntArray newIntArray( long length, int defaultValue ) + public IntArray newIntArray( long length, int defaultValue, long base ) { - return new OffHeapIntArray( length, defaultValue ); + return new OffHeapIntArray( length, defaultValue, base ); } @Override - public LongArray newLongArray( long length, long defaultValue ) + public LongArray newLongArray( long length, long defaultValue, long base ) { - return new OffHeapLongArray( length, defaultValue ); + return new OffHeapLongArray( length, defaultValue, base ); + } + + @Override + public ByteArray newByteArray( long length, byte[] defaultValue, long base ) + { + return new OffHeapByteArray( length, defaultValue, base ); } @Override @@ -144,14 +210,14 @@ public Auto( NumberArrayFactory... candidates ) } @Override - public LongArray newLongArray( long length, long defaultValue ) + public LongArray newLongArray( long length, long defaultValue, long base ) { OutOfMemoryError error = null; for ( NumberArrayFactory candidate : candidates ) { try { - return candidate.newLongArray( length, defaultValue ); + return candidate.newLongArray( length, defaultValue, base ); } catch ( OutOfMemoryError e ) { // Allright let's try the next one @@ -162,14 +228,14 @@ public LongArray newLongArray( long length, long defaultValue ) } @Override - public IntArray newIntArray( long length, int defaultValue ) + public IntArray newIntArray( long length, int defaultValue, long base ) { OutOfMemoryError error = null; for ( NumberArrayFactory candidate : candidates ) { try { - return candidate.newIntArray( length, defaultValue ); + return candidate.newIntArray( length, defaultValue, base ); } catch ( OutOfMemoryError e ) { // Allright let's try the next one @@ -179,6 +245,24 @@ public IntArray newIntArray( long length, int defaultValue ) throw error( length, 4, error ); } + @Override + public ByteArray newByteArray( long length, byte[] defaultValue, long base ) + { + OutOfMemoryError error = null; + for ( NumberArrayFactory candidate : candidates ) + { + try + { + return candidate.newByteArray( length, defaultValue, base ); + } + catch ( OutOfMemoryError e ) + { // Allright let's try the next one + error = e; + } + } + throw error( length, defaultValue.length, error ); + } + private OutOfMemoryError error( long length, int itemSize, OutOfMemoryError error ) { throw Exceptions.withMessage( error, format( "%s: Not enough memory available for allocating %s, tried %s", @@ -196,24 +280,35 @@ private OutOfMemoryError error( long length, int itemSize, OutOfMemoryError erro private final NumberArrayFactory delegate = new Auto( OFF_HEAP, HEAP ); @Override - public LongArray newLongArray( long length, long defaultValue ) + public LongArray newLongArray( long length, long defaultValue, long base ) + { + // Here we want to have the property of a dynamic array which makes some parts of the array + // live on heap, some off. At the same time we want a fixed size array. Therefore first create + // the array as a dynamic array and make it grow to the requested length. + LongArray array = newDynamicLongArray( fractionOf( length ), defaultValue ); + array.at( length-1 ); + return array; + } + + @Override + public IntArray newIntArray( long length, int defaultValue, long base ) { // Here we want to have the property of a dynamic array which makes some parts of the array // live on heap, some off. At the same time we want a fixed size array. Therefore first create - // the array as a dynamic array, make it grow to the requested length and then fixate. - DynamicLongArray array = newDynamicLongArray( fractionOf( length ), defaultValue ); - array.ensureChunkAt( length-1 ); + // the array as a dynamic array and make it grow to the requested length. + IntArray array = newDynamicIntArray( fractionOf( length ), defaultValue ); + array.at( length-1 ); return array; } @Override - public IntArray newIntArray( long length, int defaultValue ) + public ByteArray newByteArray( long length, byte[] defaultValue, long base ) { // Here we want to have the property of a dynamic array which makes some parts of the array // live on heap, some off. At the same time we want a fixed size array. Therefore first create - // the array as a dynamic array, make it grow to the requested length and then fixate. - DynamicIntArray array = newDynamicIntArray( fractionOf( length ), defaultValue ); - array.ensureChunkAt( length-1 ); + // the array as a dynamic array and make it grow to the requested length. + ByteArray array = newDynamicByteArray( fractionOf( length ), defaultValue ); + array.at( length-1 ); return array; } @@ -223,17 +318,23 @@ private long fractionOf( long length ) } @Override - public DynamicIntArray newDynamicIntArray( long chunkSize, int defaultValue ) + public IntArray newDynamicIntArray( long chunkSize, int defaultValue ) { return new DynamicIntArray( delegate, chunkSize, defaultValue ); } @Override - public DynamicLongArray newDynamicLongArray( long chunkSize, long defaultValue ) + public LongArray newDynamicLongArray( long chunkSize, long defaultValue ) { return new DynamicLongArray( delegate, chunkSize, defaultValue ); } + @Override + public ByteArray newDynamicByteArray( long chunkSize, byte[] defaultValue ) + { + return new DynamicByteArray( delegate, chunkSize, defaultValue ); + } + @Override public String toString() { 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 new file mode 100644 index 0000000000000..e6385a5e92a01 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapByteArray.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; + +public class OffHeapByteArray extends OffHeapNumberArray implements ByteArray +{ + private final byte[] defaultValue; + + protected OffHeapByteArray( long length, byte[] defaultValue, long base ) + { + super( length, defaultValue.length, base ); + this.defaultValue = defaultValue; + clear(); + } + + @Override + public void swap( long fromIndex, long toIndex, int numberOfEntries ) + { + int size = numberOfEntries * itemSize; + long intermediary = UnsafeUtil.allocateMemory( size ); + UnsafeUtil.copyMemory( address( fromIndex, 0 ), intermediary, size ); + UnsafeUtil.copyMemory( address( toIndex, 0 ), address( fromIndex, 0 ), size ); + UnsafeUtil.copyMemory( intermediary, address( toIndex, 0 ), size ); + UnsafeUtil.free( intermediary ); + } + + @Override + public void clear() + { + if ( isByteUniform( defaultValue ) ) + { + UnsafeUtil.setMemory( address, length * itemSize, defaultValue[0] ); + } + else + { + long intermediary = UnsafeUtil.allocateMemory( itemSize ); + for ( int i = 0; i < defaultValue.length; i++ ) + { + UnsafeUtil.putByte( intermediary + i, defaultValue[i] ); + } + + for ( long i = 0, adr = address; i < length; i++, adr += itemSize ) + { + UnsafeUtil.copyMemory( intermediary, adr, itemSize ); + } + UnsafeUtil.free( intermediary ); + } + } + + private boolean isByteUniform( byte[] bytes ) + { + byte reference = bytes[0]; + for ( int i = 1; i < bytes.length; i++ ) + { + if ( reference != bytes[i] ) + { + return false; + } + } + return true; + } + + @Override + public void get( long index, byte[] into ) + { + long address = address( index, 0 ); + for ( int i = 0; i < itemSize; i++, address++ ) + { + into[i] = UnsafeUtil.getByte( address ); + } + } + + @Override + public byte getByte( long index, int offset ) + { + return UnsafeUtil.getByte( address( index, offset ) ); + } + + @Override + public short getShort( long index, int offset ) + { + return UnsafeUtil.getShort( address( index, offset ) ); + } + + @Override + public int getInt( long index, int offset ) + { + return UnsafeUtil.getInt( address( index, offset ) ); + } + + @Override + public long get6BLong( long index, int offset ) + { + long address = address( index, offset ); + long low4b = (UnsafeUtil.getInt( address )) & 0xFFFFFFFFL; + long high2b = UnsafeUtil.getShort( address + Integer.BYTES ); + return low4b | (high2b << 32); + } + + @Override + public long getLong( long index, int offset ) + { + return UnsafeUtil.getLong( address( index, offset ) ); + } + + @Override + public void set( long index, byte[] value ) + { + long address = address( index, 0 ); + for ( int i = 0; i < itemSize; i++, address++ ) + { + UnsafeUtil.putByte( address, value[i] ); + } + } + + @Override + public void setByte( long index, int offset, byte value ) + { + UnsafeUtil.putByte( address( index, offset ), value ); + } + + @Override + public void setShort( long index, int offset, short value ) + { + UnsafeUtil.putShort( address( index, offset ), value ); + } + + @Override + public void setInt( long index, int offset, int value ) + { + UnsafeUtil.putInt( address( index, offset ), value ); + } + + @Override + public void set6BLong( long index, int offset, long value ) + { + long address = address( index, offset ); + UnsafeUtil.putInt( address, (int) value ); + UnsafeUtil.putShort( address + Integer.BYTES, (short) (value >>> 32) ); + } + + @Override + public void setLong( long index, int offset, long value ) + { + UnsafeUtil.putLong( address( index, offset ), value ); + } + + private long address( long index, int offset ) + { + return address + (rebase( index ) * itemSize) + offset; + } +} diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapIntArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapIntArray.java index d1a0d76c203f0..c863196b52ac0 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapIntArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapIntArray.java @@ -25,13 +25,13 @@ * Off-heap version of {@link IntArray} using {@code sun.misc.Unsafe}. Supports arrays with length beyond * Integer.MAX_VALUE. */ -public class OffHeapIntArray extends OffHeapNumberArray implements IntArray +public class OffHeapIntArray extends OffHeapRegularNumberArray implements IntArray { private final int defaultValue; - public OffHeapIntArray( long length, int defaultValue ) + public OffHeapIntArray( long length, int defaultValue, long base ) { - super( length, 2 ); + super( length, 2, base ); this.defaultValue = defaultValue; clear(); } @@ -57,7 +57,7 @@ public void clear() } else { - for ( long i = 0, adr = address; i < length; i++, adr += stride ) + for ( long i = 0, adr = address; i < length; i++, adr += itemSize ) { UnsafeUtil.putInt( adr, defaultValue ); } @@ -70,7 +70,7 @@ public void swap( long fromIndex, long toIndex, int numberOfEntries ) long fromAddress = addressOf( fromIndex ); long toAddress = addressOf( toIndex ); - for ( int i = 0; i < numberOfEntries; i++, fromAddress += stride, toAddress += stride ) + for ( int i = 0; i < numberOfEntries; i++, fromAddress += itemSize, toAddress += itemSize ) { int fromValue = UnsafeUtil.getInt( fromAddress ); UnsafeUtil.putInt( fromAddress, UnsafeUtil.getInt( toAddress ) ); diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapLongArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapLongArray.java index b3f266f5be2ab..ac19676ca664c 100644 --- a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapLongArray.java +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapLongArray.java @@ -25,13 +25,13 @@ * Off-heap version of {@link LongArray} using {@code sun.misc.Unsafe}. Supports arrays with length beyond * Integer.MAX_VALUE. */ -public class OffHeapLongArray extends OffHeapNumberArray implements LongArray +public class OffHeapLongArray extends OffHeapRegularNumberArray implements LongArray { private final long defaultValue; - public OffHeapLongArray( long length, long defaultValue ) + public OffHeapLongArray( long length, long defaultValue, long base ) { - super( length, 3 ); + super( length, 3, base ); this.defaultValue = defaultValue; clear(); } @@ -57,7 +57,7 @@ public void clear() } else { - for ( long i = 0, adr = address; i < length; i++, adr += stride ) + for ( long i = 0, adr = address; i < length; i++, adr += itemSize ) { UnsafeUtil.putLong( adr, defaultValue ); } @@ -70,7 +70,7 @@ public void swap( long fromIndex, long toIndex, int numberOfEntries ) long fromAddress = addressOf( fromIndex ); long toAddress = addressOf( toIndex ); - for ( int i = 0; i < numberOfEntries; i++, fromAddress += stride, toAddress += stride ) + for ( int i = 0; i < numberOfEntries; i++, fromAddress += itemSize, toAddress += itemSize ) { long fromValue = UnsafeUtil.getLong( fromAddress ); UnsafeUtil.putLong( fromAddress, UnsafeUtil.getLong( toAddress ) ); 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 e7e24985258d6..de499a9cab395 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 @@ -21,24 +21,18 @@ import org.neo4j.unsafe.impl.internal.dragons.UnsafeUtil; -/** - * Base class for common functionality for any {@link NumberArray} where the data lives off-heap. - */ -abstract class OffHeapNumberArray implements NumberArray +public abstract class OffHeapNumberArray> extends BaseNumberArray { protected final long address; protected final long length; - protected final int shift; - protected final int stride; private boolean closed; - protected OffHeapNumberArray( long length, int shift ) + protected OffHeapNumberArray( long length, int itemSize, long base ) { + super( itemSize, base ); UnsafeUtil.assertHasUnsafe(); this.length = length; - this.shift = shift; - this.stride = 1 << shift; - this.address = UnsafeUtil.allocateMemory( length << shift ); + this.address = UnsafeUtil.allocateMemory( length * itemSize ); } @Override @@ -47,37 +41,10 @@ public long length() return length; } - protected long addressOf( long index ) - { - if ( index < 0 || index >= length ) - { - throw new ArrayIndexOutOfBoundsException( "Requested index " + index + ", but length is " + length ); - } - return address + (index << shift); - } - - protected boolean isByteUniform( long value ) - { - byte any = 0; // assignment not really needed - for ( int i = 0; i < stride; i++ ) - { - byte test = (byte)(value >>> 8*i); - if ( i == 0 ) - { - any = test; - } - else if ( test != any ) - { - return false; - } - } - return true; - } - @Override public void acceptMemoryStatsVisitor( MemoryStatsVisitor visitor ) { - visitor.offHeapUsage( length * stride ); + visitor.offHeapUsage( length * itemSize ); } @Override diff --git a/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapRegularNumberArray.java b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapRegularNumberArray.java new file mode 100644 index 0000000000000..6b65309477d92 --- /dev/null +++ b/community/kernel/src/main/java/org/neo4j/unsafe/impl/batchimport/cache/OffHeapRegularNumberArray.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +/** + * Base class for common functionality for any {@link NumberArray} where the data lives off-heap. + */ +abstract class OffHeapRegularNumberArray> extends OffHeapNumberArray +{ + protected final int shift; + + protected OffHeapRegularNumberArray( long length, int shift, long base ) + { + super( length, 1 << shift, base ); + this.shift = shift; + } + + protected long addressOf( long index ) + { + index = rebase( index ); + if ( index < 0 || index >= length ) + { + throw new ArrayIndexOutOfBoundsException( "Requested index " + index + ", but length is " + length ); + } + return address + (index << shift); + } + + protected boolean isByteUniform( long value ) + { + byte any = 0; // assignment not really needed + for ( int i = 0; i < itemSize; i++ ) + { + byte test = (byte)(value >>> 8*i); + if ( i == 0 ) + { + any = test; + } + else if ( test != any ) + { + return false; + } + } + return true; + } +} diff --git a/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArrayTest.java b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArrayTest.java new file mode 100644 index 0000000000000..1729bad13c237 --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/ByteArrayTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ByteArrayTest +{ + @Test + public void shouldSetAndGetBasicTypes() throws Exception + { + // GIVEN + ByteArray array = newArray( 100, new byte[15] ); + + // WHEN + array.setByte( 0, 0, (byte) 123 ); + array.setShort( 0, 1, (short) 1234 ); + array.setInt( 0, 5, 12345 ); + array.setLong( 0, 9, Long.MAX_VALUE - 100 ); + + // THEN + assertEquals( (byte) 123, array.getByte( 0, 0 ) ); + assertEquals( (short) 1234, array.getShort( 0, 1 ) ); + assertEquals( 12345, array.getInt( 0, 5 ) ); + assertEquals( Long.MAX_VALUE - 100, array.getLong( 0, 9 ) ); + } + + @Test + public void shouldDetectMinusOne() throws Exception + { + // GIVEN + ByteArray array = newArray( 100, new byte[15] ); + + // WHEN + array.set6BLong( 10, 2, -1 ); + array.set6BLong( 10, 8, -1 ); + + // THEN + assertEquals( -1L, array.get6BLong( 10, 2 ) ); + assertEquals( -1L, array.get6BLong( 10, 8 ) ); + } + + private ByteArray newArray( int length, byte[] defaultValue ) + { + return NumberArrayFactory.HEAP.newByteArray( length, defaultValue ); + } +} diff --git a/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCacheTest.java b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCacheTest.java index 3e46a1104acd9..4e678fbdfa2a9 100644 --- a/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCacheTest.java +++ b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NodeRelationshipCacheTest.java @@ -19,6 +19,7 @@ */ package org.neo4j.unsafe.impl.batchimport.cache; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,6 +36,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -43,11 +45,14 @@ import static java.lang.Math.max; import static java.lang.System.currentTimeMillis; +import static org.neo4j.graphdb.Direction.OUTGOING; + @RunWith( Parameterized.class ) public class NodeRelationshipCacheTest { @Parameterized.Parameter( 0 ) public long base; + private NodeRelationshipCache cache; @Parameterized.Parameters public static Collection data() @@ -62,7 +67,7 @@ public static Collection data() public void shouldReportCorrectNumberOfDenseNodes() throws Exception { // GIVEN - NodeRelationshipCache cache = new NodeRelationshipCache( NumberArrayFactory.AUTO, 5, base ); + cache = new NodeRelationshipCache( NumberArrayFactory.AUTO, 5, 100, base ); increment( cache, 2, 10 ); increment( cache, 5, 2 ); increment( cache, 7, 12 ); @@ -85,21 +90,21 @@ public void shouldGoThroughThePhases() throws Exception { // GIVEN int nodeCount = 10; - NodeRelationshipCache link = new NodeRelationshipCache( NumberArrayFactory.OFF_HEAP, 20, base ); - incrementRandomCounts( link, nodeCount, nodeCount*20 ); + cache = new NodeRelationshipCache( NumberArrayFactory.OFF_HEAP, 20, 100, base ); + incrementRandomCounts( cache, nodeCount, nodeCount*20 ); // Test sparse node semantics { - long node = findNode( link, nodeCount, false ); - testNode( link, node, -1, null ); + long node = findNode( cache, nodeCount, false ); + testNode( cache, node, -1, null ); } // Test dense node semantics { - long node = findNode( link, nodeCount, true ); - testNode( link, node, 4, Direction.OUTGOING ); - testNode( link, node, 4, Direction.INCOMING ); - testNode( link, node, 2, Direction.OUTGOING ); + long node = findNode( cache, nodeCount, true ); + testNode( cache, node, 4, Direction.OUTGOING ); + testNode( cache, node, 4, Direction.INCOMING ); + testNode( cache, node, 2, Direction.OUTGOING ); } } @@ -108,19 +113,19 @@ public void shouldAddGroupAfterTheFirst() throws Exception { // GIVEN a dense node long denseNode = 0; - NodeRelationshipCache link = new NodeRelationshipCache( NumberArrayFactory.AUTO, 1, base ); - link.incrementCount( denseNode ); - link.getAndPutRelationship( denseNode, 0, Direction.OUTGOING, 0, true ); + cache = new NodeRelationshipCache( NumberArrayFactory.AUTO, 1, 100, base ); + cache.incrementCount( denseNode ); + cache.getAndPutRelationship( denseNode, 0, Direction.OUTGOING, 0, true ); // WHEN - link.getAndPutRelationship( denseNode, 1, Direction.INCOMING, 1, true ); + cache.getAndPutRelationship( denseNode, 1, Direction.INCOMING, 1, true ); // just fill more data into the groups - link.getAndPutRelationship( denseNode, 0, Direction.INCOMING, 2, true ); - link.getAndPutRelationship( denseNode, 1, Direction.OUTGOING, 3, true ); + cache.getAndPutRelationship( denseNode, 0, Direction.INCOMING, 2, true ); + cache.getAndPutRelationship( denseNode, 1, Direction.OUTGOING, 3, true ); // THEN GroupVisitor visitor = mock( GroupVisitor.class ); - assertEquals( 0L, link.getFirstRel( denseNode, visitor ) ); + assertEquals( 0L, cache.getFirstRel( denseNode, visitor ) ); InOrder order = inOrder( visitor ); order.verify( visitor ).visit( denseNode, 0, base + 1L, 0L, 2L, -1L ); order.verify( visitor ).visit( denseNode, 1, -1L, 3L, 1L, -1L ); @@ -132,19 +137,19 @@ public void shouldAddGroupBeforeTheFirst() throws Exception { // GIVEN a dense node long denseNode = 0; - NodeRelationshipCache link = new NodeRelationshipCache( NumberArrayFactory.AUTO, 1, base ); - link.incrementCount( denseNode ); - link.getAndPutRelationship( denseNode, 1, Direction.INCOMING, 1, true ); + cache = new NodeRelationshipCache( NumberArrayFactory.AUTO, 1, 100, base ); + cache.incrementCount( denseNode ); + cache.getAndPutRelationship( denseNode, 1, Direction.INCOMING, 1, true ); // WHEN - link.getAndPutRelationship( denseNode, 0, Direction.OUTGOING, 0, true ); + cache.getAndPutRelationship( denseNode, 0, Direction.OUTGOING, 0, true ); // just fill more data into the groups - link.getAndPutRelationship( denseNode, 0, Direction.INCOMING, 2, true ); - link.getAndPutRelationship( denseNode, 1, Direction.OUTGOING, 3, true ); + cache.getAndPutRelationship( denseNode, 0, Direction.INCOMING, 2, true ); + cache.getAndPutRelationship( denseNode, 1, Direction.OUTGOING, 3, true ); // THEN GroupVisitor visitor = mock( GroupVisitor.class ); - assertEquals( 0L, link.getFirstRel( denseNode, visitor ) ); + assertEquals( 0L, cache.getFirstRel( denseNode, visitor ) ); InOrder order = inOrder( visitor ); order.verify( visitor ).visit( denseNode, 0, base + 1L, 0L, 2L, -1L ); order.verify( visitor ).visit( denseNode, 1, -1L, 3L, 1L, -1L ); @@ -156,28 +161,127 @@ public void shouldAddGroupInTheMiddleIfTwo() throws Exception { // GIVEN a dense node long denseNode = 0; - NodeRelationshipCache link = new NodeRelationshipCache( NumberArrayFactory.AUTO, 1, base ); - link.incrementCount( denseNode ); - link.getAndPutRelationship( denseNode, 0, Direction.OUTGOING, 0, true ); - link.getAndPutRelationship( denseNode, 2, Direction.OUTGOING, 1, true ); + cache = new NodeRelationshipCache( NumberArrayFactory.AUTO, 1, 100, base ); + cache.incrementCount( denseNode ); + cache.getAndPutRelationship( denseNode, 0, Direction.OUTGOING, 0, true ); + cache.getAndPutRelationship( denseNode, 2, Direction.OUTGOING, 1, true ); // WHEN - link.getAndPutRelationship( denseNode, 1, Direction.INCOMING, 2, true ); + cache.getAndPutRelationship( denseNode, 1, Direction.INCOMING, 2, true ); // just fill more data into the groups - link.getAndPutRelationship( denseNode, 0, Direction.INCOMING, 3, true ); - link.getAndPutRelationship( denseNode, 1, Direction.OUTGOING, 4, true ); - link.getAndPutRelationship( denseNode, 2, Direction.INCOMING, 5, true ); - link.getAndPutRelationship( denseNode, 1, Direction.BOTH, 6, true ); + cache.getAndPutRelationship( denseNode, 0, Direction.INCOMING, 3, true ); + cache.getAndPutRelationship( denseNode, 1, Direction.OUTGOING, 4, true ); + cache.getAndPutRelationship( denseNode, 2, Direction.INCOMING, 5, true ); + cache.getAndPutRelationship( denseNode, 1, Direction.BOTH, 6, true ); // THEN GroupVisitor visitor = mock( GroupVisitor.class ); - assertEquals( 0L, link.getFirstRel( denseNode, visitor ) ); + assertEquals( 0L, cache.getFirstRel( denseNode, visitor ) ); verify( visitor ).visit( denseNode, 0, base + 2L, 0L, 3L, -1L ); verify( visitor ).visit( denseNode, 1, base + 1L, 4L, 2L, 6L ); verify( visitor ).visit( denseNode, 2, -1L, 1L, 5L, -1L ); verifyNoMoreInteractions( visitor ); } + @Test + public void shouldClearRelationships() throws Exception + { + // GIVEN + cache = new NodeRelationshipCache( NumberArrayFactory.AUTO, 1, 100, base ); + int nodes = 100; + Direction[] directions = Direction.values(); + GroupVisitor groupVisitor = mock( GroupVisitor.class ); + for ( int i = 0; i < nodes; i++ ) + { + assertEquals( -1L, cache.getFirstRel( nodes, groupVisitor ) ); + cache.incrementCount( i ); + cache.getAndPutRelationship( i, i % 5, directions[i % directions.length], + random.nextInt( 1_000_000 ), true ); + assertEquals( 1, cache.getCount( i, i % 5, directions[i % directions.length] ) ); + } + + // WHEN + cache.clearRelationships(); + + // THEN + for ( int i = 0; i < nodes; i++ ) + { + assertEquals( -1L, cache.getFirstRel( nodes, groupVisitor ) ); + assertEquals( 1, cache.getCount( i, i % 5, directions[i % directions.length] ) ); + } + } + + @Test + public void shouldGetAndPutRelationshipAroundChunkEdge() throws Exception + { + // GIVEN + cache = new NodeRelationshipCache( NumberArrayFactory.HEAP, 10 ); + + // WHEN + long nodeId = 1_000_000 - 1; + int type = 0; + Direction direction = Direction.OUTGOING; + long relId = 10; + cache.getAndPutRelationship( nodeId, type, direction, relId, false ); + + // THEN + assertEquals( relId, cache.getFirstRel( nodeId, mock( GroupVisitor.class ) ) ); + } + + @Test + public void shouldPutRandomStuff() throws Exception + { + // GIVEN + cache = new NodeRelationshipCache( NumberArrayFactory.HEAP, 10, 1000, base ); + + // WHEN + for ( int i = 0; i < 10_000; i++ ) + { + cache.getAndPutRelationship( random.nextInt( 100_000 ), random.nextInt( 5 ), + Direction.OUTGOING, random.nextInt( 1_000_000 ), true ); + } + } + + @Test + public void shouldPut6ByteRelationshipIds() throws Exception + { + // GIVEN + cache = new NodeRelationshipCache( NumberArrayFactory.HEAP, 1, 100, base ); + long sparseNode = 0; + long denseNode = 1; + long relationshipId = (1L << 48) - 2; + cache.incrementCount( denseNode ); + + // WHEN + assertEquals( -1L, cache.getAndPutRelationship( sparseNode, 0, OUTGOING, relationshipId, false ) ); + assertEquals( -1L, cache.getAndPutRelationship( denseNode, 0, OUTGOING, relationshipId, false ) ); + + // THEN + GroupVisitor groupVisitor = mock( GroupVisitor.class ); + assertEquals( relationshipId, cache.getAndPutRelationship( sparseNode, 0, OUTGOING, 1, false ) ); + assertEquals( relationshipId, cache.getAndPutRelationship( denseNode, 0, OUTGOING, 1, false ) ); + } + + @Test + public void shouldFailFastIfTooBigRelationshipId() throws Exception + { + // GIVEN + cache = new NodeRelationshipCache( NumberArrayFactory.HEAP, 1, 100, base ); + + // WHEN + cache.getAndPutRelationship( 0, 0, OUTGOING, (1L << 48) - 2, false ); + try + { + cache.getAndPutRelationship( 0, 0, OUTGOING, (1L << 48) - 1, false ); + fail( "Should fail" ); + } + catch ( IllegalArgumentException e ) + { + // THEN Good + assertTrue( e.getMessage().contains( "max" ) ); + } + } + private void testNode( NodeRelationshipCache link, long node, int type, Direction direction ) { int count = link.getCount( node, type, direction ); @@ -218,6 +322,12 @@ public void before() random = new Random( seed ); } + @After + public void after() + { + cache.close(); + } + private void increment( NodeRelationshipCache cache, long node, int count ) { for ( int i = 0; i < count; i++ ) diff --git a/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactoryTest.java b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactoryTest.java index 6c0cf8f76fec8..527f962c9401d 100644 --- a/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactoryTest.java +++ b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayFactoryTest.java @@ -56,7 +56,7 @@ public void shouldPickFirstAvailableCandidateLongArrayWhenSomeDontHaveEnoughMemo { // GIVEN NumberArrayFactory lowMemoryFactory = mock( NumberArrayFactory.class ); - doThrow( OutOfMemoryError.class ).when( lowMemoryFactory ).newLongArray( anyLong(), anyLong() ); + doThrow( OutOfMemoryError.class ).when( lowMemoryFactory ).newLongArray( anyLong(), anyLong(), anyLong() ); NumberArrayFactory factory = new NumberArrayFactory.Auto( lowMemoryFactory, NumberArrayFactory.HEAP ); // WHEN @@ -64,7 +64,7 @@ public void shouldPickFirstAvailableCandidateLongArrayWhenSomeDontHaveEnoughMemo array.set( 1*KILO-10, 12345 ); // THEN - verify( lowMemoryFactory, times( 1 ) ).newLongArray( 1*KILO, -1 ); + verify( lowMemoryFactory, times( 1 ) ).newLongArray( 1*KILO, -1, 0 ); assertTrue( array instanceof HeapLongArray ); assertEquals( 12345, array.get( 1*KILO-10 ) ); } @@ -74,7 +74,7 @@ public void shouldThrowOomOnNotEnoughMemory() throws Exception { // GIVEN NumberArrayFactory lowMemoryFactory = mock( NumberArrayFactory.class ); - doThrow( OutOfMemoryError.class ).when( lowMemoryFactory ).newLongArray( anyLong(), anyLong() ); + doThrow( OutOfMemoryError.class ).when( lowMemoryFactory ).newLongArray( anyLong(), anyLong(), anyLong() ); NumberArrayFactory factory = new NumberArrayFactory.Auto( lowMemoryFactory ); // WHEN @@ -109,7 +109,7 @@ public void shouldPickFirstAvailableCandidateIntArrayWhenSomeDontHaveEnoughMemor { // GIVEN NumberArrayFactory lowMemoryFactory = mock( NumberArrayFactory.class ); - doThrow( OutOfMemoryError.class ).when( lowMemoryFactory ).newIntArray( anyLong(), anyInt() ); + doThrow( OutOfMemoryError.class ).when( lowMemoryFactory ).newIntArray( anyLong(), anyInt(), anyInt() ); NumberArrayFactory factory = new NumberArrayFactory.Auto( lowMemoryFactory, NumberArrayFactory.HEAP ); // WHEN @@ -117,7 +117,7 @@ public void shouldPickFirstAvailableCandidateIntArrayWhenSomeDontHaveEnoughMemor array.set( 1*KILO-10, 12345 ); // THEN - verify( lowMemoryFactory, times( 1 ) ).newIntArray( 1*KILO, -1 ); + verify( lowMemoryFactory, times( 1 ) ).newIntArray( 1*KILO, -1, 0 ); assertTrue( array instanceof HeapIntArray ); assertEquals( 12345, array.get( 1*KILO-10 ) ); } diff --git a/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayTest.java b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayTest.java new file mode 100644 index 0000000000000..8fd6701c2101a --- /dev/null +++ b/community/kernel/src/test/java/org/neo4j/unsafe/impl/batchimport/cache/NumberArrayTest.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.unsafe.impl.batchimport.cache; + +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +import org.neo4j.test.RandomRule; + +import static org.junit.Assert.assertEquals; + +import static org.neo4j.helpers.ArrayUtil.array; +import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.AUTO; +import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.CHUNKED_FIXED_SIZE; +import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.HEAP; +import static org.neo4j.unsafe.impl.batchimport.cache.NumberArrayFactory.OFF_HEAP; + +@RunWith( Parameterized.class ) +public class NumberArrayTest +{ + @FunctionalInterface + interface Writer> + { + void write( N array, int index, Object value ); + } + + @FunctionalInterface + interface Reader> + { + Object read( N array, int index ); + } + + private static final int INDEXES = 50_000; + + @Parameters + public static Collection arrays() + { + Collection list = new ArrayList<>(); + for ( NumberArrayFactory factory : array( HEAP, OFF_HEAP, AUTO, CHUNKED_FIXED_SIZE ) ) + { + list.add( line( + factory.newIntArray( INDEXES, -1 ), + (random) -> random.nextInt( 1_000_000_000 ), + (array, index, value) -> array.set( index, (Integer) value ), + (array, index) -> array.get( index ) ) ); + list.add( line( + factory.newDynamicIntArray( INDEXES / 100, -1 ), + (random) -> random.nextInt( 1_000_000_000 ), + (array, index, value) -> array.set( index, (Integer) value ), + (array, index) -> array.get( index ) ) ); + + list.add( line( + factory.newLongArray( INDEXES, -1 ), + (random) -> random.nextLong( 1_000_000_000 ), + (array, index, value) -> array.set( index, (Long) value ), + (array, index) -> array.get( index ) ) ); + list.add( line( + factory.newDynamicLongArray( INDEXES / 100, -1 ), + (random) -> random.nextLong( 1_000_000_000 ), + (array, index, value) -> array.set( index, (Long) value ), + (array, index) -> array.get( index ) ) ); + + list.add( line( + factory.newByteArray( INDEXES, new byte[] {-1, -1, -1, -1, -1} ), + (random) -> random.nextInt( 1_000_000_000 ), + (array, index, value) -> array.setInt( index, 1, (Integer) value ), + (array, index) -> array.getInt( index, 1 ) ) ); + list.add( line( + factory.newDynamicByteArray( INDEXES / 100, new byte[] {-1, -1, -1, -1, -1} ), + (random) -> random.nextInt( 1_000_000_000 ), + (array, index, value) -> array.setInt( index, 1, (Integer) value ), + (array, index) -> array.getInt( index, 1 ) ) ); + } + return list; + } + + private static > Object[] line( N array, Function valueGenerator, + Writer writer, Reader reader ) + { + return new Object[] {array, valueGenerator, writer, reader}; + } + + @Rule + public RandomRule random = new RandomRule().withSeed( 1459254570461L ); + + @Parameter( 0 ) + public NumberArray array; + @Parameter( 1 ) + public Function valueGenerator; + @SuppressWarnings( "rawtypes" ) + @Parameter( 2 ) + public Writer writer; + @SuppressWarnings( "rawtypes" ) + @Parameter( 3 ) + public Reader reader; + + @SuppressWarnings( "unchecked" ) + @Test + public void shouldGetAndSetRandomItems() throws Exception + { + // GIVEN + Map key = new HashMap<>(); + Object defaultValue = reader.read( array, 0 ); + + // WHEN + for ( int i = 0; i < 100_000; i++ ) + { + int index = random.nextInt( INDEXES ); + Object value = valueGenerator.apply( random ); + writer.write( i % 2 == 0 ? array : array.at( index ), index, value ); + key.put( index, value ); + } + + // THEN + for ( int index = 0; index < INDEXES; index++ ) + { + Object value = reader.read( index % 2 == 0 ? array : array.at( index ), index ); + Object expectedValue = key.containsKey( index ) ? key.get( index ) : defaultValue; + assertEquals( expectedValue, value ); + } + } + + @After + public void after() + { + array.close(); + } +}