Skip to content

Commit

Permalink
Use cursor directly instead of intermediate iterator
Browse files Browse the repository at this point in the history
By having direct access to the cursor we can get a significant speedup
instead of delegating via an iterator when doing label scans.
  • Loading branch information
pontusmelke committed Jan 7, 2018
1 parent de44791 commit 3a2a6d2
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 79 deletions.
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2002-2018 "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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.index.labelscan;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collection;

import org.neo4j.cursor.RawCursor;
import org.neo4j.index.internal.gbptree.Hit;

/**
* Base class for iterator and index-progressor of label scans.
*/
abstract class LabelScanValueIndexAccessor
{
/**
* {@link RawCursor} to lazily read new {@link LabelScanValue} from.
*/
protected final RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException> cursor;
/**
* Remove provided cursor from this collection when iterator is exhausted.
*/
private final Collection<RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException>> toRemoveFromWhenClosed;
/**
* Current base nodeId, i.e. the {@link LabelScanKey#idRange} of the current {@link LabelScanKey}.
*/
long baseNodeId;
/**
* Bit set of the current {@link LabelScanValue}.
*/
protected long bits;
/**
* LabelId of previously retrieved {@link LabelScanKey}, for debugging and asserting purposes.
*/
private int prevLabel = -1;
/**
* IdRange of previously retrieved {@link LabelScanKey}, for debugging and asserting purposes.
*/
private long prevRange = -1;
/**
* Indicate provided cursor has been closed.
*/
private boolean closed;

LabelScanValueIndexAccessor(
Collection<RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException>> toRemoveFromWhenClosed,
RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException> cursor )
{
this.toRemoveFromWhenClosed = toRemoveFromWhenClosed;
this.cursor = cursor;
}

boolean keysInOrder( LabelScanKey key )
{
assert key.labelId >= prevLabel : "Expected to get ordered results, got " + key +
" where previous label was " + prevLabel;
assert key.idRange > prevRange : "Expected to get ordered results, got " + key +
" where previous range was " + prevRange;
prevLabel = key.labelId;
prevRange = key.idRange;
// Made as a method returning boolean so that it can participate in an assert call.
return true;
}

public void close()
{
if ( !closed )
{
try
{
cursor.close();
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}
finally
{
toRemoveFromWhenClosed.remove( cursor );
closed = true;
}
}
}
}
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2002-2018 "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 <http://www.gnu.org/licenses/>.
*/
package org.neo4j.kernel.impl.index.labelscan;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collection;

import org.neo4j.cursor.RawCursor;
import org.neo4j.graphdb.Resource;
import org.neo4j.index.internal.gbptree.Hit;
import org.neo4j.storageengine.api.schema.IndexProgressor;

/**
* {@link IndexProgressor} which steps over multiple {@link LabelScanValue} and for each
* iterate over each set bit, returning actual node ids, i.e. {@code nodeIdRange+bitOffset}.
*
*/
class LabelScanValueIndexProgressor extends LabelScanValueIndexAccessor implements IndexProgressor, Resource
{

private final NodeLabelClient client;

LabelScanValueIndexProgressor( RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException> cursor,
Collection<RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException>> toRemoveFromWhenClosed,
NodeLabelClient client )
{
super( toRemoveFromWhenClosed, cursor );
this.client = client;
}

/**
* @return next node id in the current {@link LabelScanValue} or, if current value exhausted,
* goes to next {@link LabelScanValue} from {@link RawCursor}. Returns {@code true} if next node id
* was found, otherwise {@code false}.
*/
@Override
public boolean next()
{
while ( true )
{
if ( bits != 0 )
{
int delta = Long.numberOfTrailingZeros( bits );
bits &= bits - 1;
if ( client.acceptNode( baseNodeId + delta, null ) )
{
return true;
}
}
try
{
if ( !cursor.next() )
{
close();
return false;
}
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}

Hit<LabelScanKey,LabelScanValue> hit = cursor.get();
baseNodeId = hit.key().idRange * LabelScanValue.RANGE_SIZE;
bits = hit.value().bits;

//noinspection AssertWithSideEffects
assert keysInOrder( hit.key() );
}
}
}
Expand Up @@ -22,6 +22,7 @@
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.util.Collection; import java.util.Collection;
import java.util.NoSuchElementException;


import org.neo4j.collection.primitive.PrimitiveLongCollections; import org.neo4j.collection.primitive.PrimitiveLongCollections;
import org.neo4j.collection.primitive.PrimitiveLongIterator; import org.neo4j.collection.primitive.PrimitiveLongIterator;
Expand All @@ -36,63 +37,56 @@
* The provided {@link RawCursor} is managed externally, e.g. {@link NativeLabelScanReader}, * The provided {@link RawCursor} is managed externally, e.g. {@link NativeLabelScanReader},
* this because implemented interface lacks close-method. * this because implemented interface lacks close-method.
*/ */
class LabelScanValueIterator extends PrimitiveLongCollections.PrimitiveLongBaseIterator implements PrimitiveLongResourceIterator class LabelScanValueIterator extends LabelScanValueIndexAccessor implements PrimitiveLongResourceIterator
{ {
/** private boolean hasNextDecided;
* {@link RawCursor} to lazily read new {@link LabelScanValue} from. private boolean hasNext;
*/ protected long next;
private final RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException> cursor;


/** @Override
* Current base nodeId, i.e. the {@link LabelScanKey#idRange} of the current {@link LabelScanKey}. public boolean hasNext()
*/ {
private long baseNodeId; if ( !hasNextDecided )

{
/** hasNext = fetchNext();
* Bit set of the current {@link LabelScanValue}. hasNextDecided = true;
*/ }
private long bits; return hasNext;

}
/**
* LabelId of previously retrieved {@link LabelScanKey}, for debugging and asserting purposes.
*/
private int prevLabel = -1;

/**
* IdRange of previously retrieved {@link LabelScanKey}, for debugging and asserting purposes.
*/
private long prevRange = -1;

/**
* Remove provided cursor from this collection when iterator is exhausted.
*/
private final Collection<RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException>> toRemoveFromWhenClosed;


/** @Override
* Indicate provided cursor has been closed. public long next()
*/ {
private boolean closed; if ( !hasNext() )
{
throw new NoSuchElementException( "No more elements in " + this );
}
hasNextDecided = false;
return next;
}


LabelScanValueIterator( RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException> cursor, LabelScanValueIterator( RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException> cursor,
Collection<RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException>> toRemoveFromWhenClosed ) Collection<RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException>> toRemoveFromWhenClosed )
{ {
this.cursor = cursor; super( toRemoveFromWhenClosed, cursor );
this.toRemoveFromWhenClosed = toRemoveFromWhenClosed;
} }


/** /**
* @return next node id in the current {@link LabelScanValue} or, if current value exhausted, * @return next node id in the current {@link LabelScanValue} or, if current value exhausted,
* goes to next {@link LabelScanValue} from {@link RawCursor}. Returns {@code true} if next node id * goes to next {@link LabelScanValue} from {@link RawCursor}. Returns {@code true} if next node id
* was found, otherwise {@code false}. * was found, otherwise {@code false}.
*/ */
@Override
protected boolean fetchNext() protected boolean fetchNext()
{ {
while ( true ) while ( true )
{ {
if ( bits != 0 ) if ( bits != 0 )
{ {
return nextFromCurrent(); int delta = Long.numberOfTrailingZeros( bits );
bits &= bits - 1;
next = baseNodeId + delta ;
hasNext = true;
return true;
} }


try try
Expand All @@ -116,44 +110,4 @@ protected boolean fetchNext()
assert keysInOrder( hit.key() ); assert keysInOrder( hit.key() );
} }
} }

private boolean keysInOrder( LabelScanKey key )
{
assert key.labelId >= prevLabel : "Expected to get ordered results, got " + key +
" where previous label was " + prevLabel;
assert key.idRange > prevRange : "Expected to get ordered results, got " + key +
" where previous range was " + prevRange;
prevLabel = key.labelId;
prevRange = key.idRange;
// Made as a method returning boolean so that it can participate in an assert call.
return true;
}

private boolean nextFromCurrent()
{
int delta = Long.numberOfTrailingZeros( bits );
bits &= bits - 1;
return next( baseNodeId + delta );
}

@Override
public void close()
{
if ( !closed )
{
try
{
cursor.close();
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}
finally
{
toRemoveFromWhenClosed.remove( cursor );
closed = true;
}
}
}
} }
Expand Up @@ -32,6 +32,8 @@
import org.neo4j.graphdb.index.Index; import org.neo4j.graphdb.index.Index;
import org.neo4j.index.internal.gbptree.GBPTree; import org.neo4j.index.internal.gbptree.GBPTree;
import org.neo4j.index.internal.gbptree.Hit; import org.neo4j.index.internal.gbptree.Hit;
import org.neo4j.kernel.impl.index.schema.NumberHitIndexProgressor;
import org.neo4j.storageengine.api.schema.IndexProgressor;
import org.neo4j.storageengine.api.schema.LabelScanReader; import org.neo4j.storageengine.api.schema.LabelScanReader;


/** /**
Expand Down Expand Up @@ -108,6 +110,23 @@ public PrimitiveLongResourceIterator nodesWithAllLabels( int... labelIds )
return new CompositeLabelScanValueIterator( iterators, true ); return new CompositeLabelScanValueIterator( iterators, true );
} }


public LabelScanValueIndexProgressor nodesWithLabelIndex( IndexProgressor.NodeLabelClient client,
int labelId )
{
RawCursor<Hit<LabelScanKey,LabelScanValue>,IOException> cursor;
try
{
cursor = seekerForLabel( labelId );
openCursors.add( cursor );
}
catch ( IOException e )
{
throw new UncheckedIOException( e );
}

return new LabelScanValueIndexProgressor( cursor, openCursors, client );
}

private List<PrimitiveLongResourceIterator> iteratorsForLabels( int[] labelIds ) private List<PrimitiveLongResourceIterator> iteratorsForLabels( int[] labelIds )
{ {
List<PrimitiveLongResourceIterator> iterators = new ArrayList<>(); List<PrimitiveLongResourceIterator> iterators = new ArrayList<>();
Expand Down
Expand Up @@ -139,8 +139,9 @@ public final void nodeLabelScan( int label, org.neo4j.internal.kernel.api.NodeLa
{ {
ktx.assertOpen(); ktx.assertOpen();


((NodeLabelIndexCursor) cursor).setRead( this ); NodeLabelIndexCursor indexCursor = (NodeLabelIndexCursor) cursor;
labelScan( (NodeLabelIndexCursor) cursor, labelScanReader().nodesWithLabel( label ) ); indexCursor.setRead( this );
indexCursor.initialize( labelScanReader().nodesWithLabelIndex( indexCursor, label ) );
} }


@Override @Override
Expand Down

0 comments on commit 3a2a6d2

Please sign in to comment.