org.neo4j.kernel.impl.util.collection.MutableLinearProbeLongHashSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of neo4j-kernel Show documentation
Show all versions of neo4j-kernel Show documentation
Neo4j kernel is a lightweight, embedded Java database designed to
store data structured as graphs rather than tables. For more
information, see http://neo4j.org.
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.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.kernel.impl.util.collection;
import org.eclipse.collections.api.LongIterable;
import org.eclipse.collections.api.block.procedure.primitive.LongProcedure;
import org.eclipse.collections.api.iterator.MutableLongIterator;
import org.eclipse.collections.api.multimap.MutableMultimap;
import org.eclipse.collections.api.set.primitive.ImmutableLongSet;
import org.eclipse.collections.api.set.primitive.LongSet;
import org.eclipse.collections.api.set.primitive.MutableLongSet;
import org.eclipse.collections.impl.factory.Multimaps;
import org.eclipse.collections.impl.set.mutable.primitive.SynchronizedLongSet;
import org.eclipse.collections.impl.set.mutable.primitive.UnmodifiableLongSet;
import org.neo4j.graphdb.Resource;
import org.neo4j.memory.MemoryTracker;
import org.neo4j.util.VisibleForTesting;
import static java.util.Objects.requireNonNull;
import static org.neo4j.util.Preconditions.requirePowerOfTwo;
/**
* Off heap implementation of long hash set.
*
* - It is not thread-safe
*
- It has to be closed to prevent native memory leakage
*
- Iterators returned by this set are fail-fast
*
*/
class MutableLinearProbeLongHashSet extends AbstractLinearProbeLongHashSet implements MutableLongSet, Resource
{
static final int DEFAULT_CAPACITY = 32;
static final int REMOVALS_RATIO = 4;
private static final double LOAD_FACTOR = 0.75;
private final MemoryAllocator allocator;
private final MemoryTracker memoryTracker;
private final MutableMultimap frozenCopies = Multimaps.mutable.list.empty();
private int resizeOccupancyThreshold;
private int resizeRemovalsThreshold;
private int removals;
private boolean frozen;
MutableLinearProbeLongHashSet( MemoryAllocator allocator, MemoryTracker memoryTracker )
{
this.allocator = requireNonNull( allocator );
this.memoryTracker = memoryTracker;
allocateMemory( DEFAULT_CAPACITY );
}
@Override
public boolean add( long element )
{
++modCount;
if ( element == 0 )
{
final boolean hadZero = hasZero;
hasZero = true;
return hadZero != hasZero;
}
if ( element == 1 )
{
final boolean hadOne = hasOne;
hasOne = true;
return hadOne != hasOne;
}
return addToMemory( element );
}
@Override
public boolean addAll( long... elements )
{
++modCount;
final int prevSize = size();
for ( final long element : elements )
{
add( element );
}
return prevSize != size();
}
@Override
public boolean addAll( LongIterable elements )
{
++modCount;
final int prevSize = size();
elements.forEach( this::add );
return prevSize != size();
}
@Override
public boolean remove( long element )
{
++modCount;
if ( element == 0 )
{
final boolean hadZero = hasZero;
hasZero = false;
return hadZero != hasZero;
}
if ( element == 1 )
{
final boolean hadOne = hasOne;
hasOne = false;
return hadOne != hasOne;
}
return removeFromMemory( element );
}
@Override
public boolean removeAll( LongIterable elements )
{
++modCount;
final int prevSize = size();
elements.forEach( this::remove );
return prevSize != size();
}
@Override
public boolean removeAll( long... elements )
{
++modCount;
final int prevSize = size();
for ( final long element : elements )
{
remove( element );
}
return prevSize != size();
}
@Override
public boolean retainAll( LongIterable elements )
{
throw new UnsupportedOperationException();
}
@Override
public boolean retainAll( long... source )
{
throw new UnsupportedOperationException();
}
@Override
public void clear()
{
++modCount;
copyIfFrozen();
memory.clear();
hasZero = false;
hasOne = false;
elementsInMemory = 0;
removals = 0;
}
@Override
public MutableLongIterator longIterator()
{
return new FailFastIterator();
}
@Override
public void close()
{
++modCount;
if ( memory != null )
{
frozenCopies.forEachKeyMultiValues( ( mem, copies ) ->
{
mem.free( memoryTracker );
copies.forEach( FrozenCopy::invalidate );
} );
if ( !frozenCopies.containsKey( memory ) )
{
memory.free( memoryTracker );
}
memory = null;
frozenCopies.clear();
}
}
@Override
public MutableLongSet tap( LongProcedure procedure )
{
each( procedure );
return this;
}
@Override
public MutableLongSet with( long element )
{
add( element );
return this;
}
@Override
public MutableLongSet without( long element )
{
remove( element );
return this;
}
@Override
public MutableLongSet withAll( LongIterable elements )
{
addAll( elements );
return this;
}
@Override
public MutableLongSet withoutAll( LongIterable elements )
{
removeAll( elements );
return this;
}
@Override
public MutableLongSet asUnmodifiable()
{
return new UnmodifiableLongSet( this );
}
@Override
public MutableLongSet asSynchronized()
{
return new SynchronizedLongSet( this );
}
@Override
public LongSet freeze()
{
frozen = true;
final FrozenCopy frozenCopy = new FrozenCopy();
frozenCopies.put( memory, frozenCopy );
return frozenCopy;
}
@Override
public ImmutableLongSet toImmutable()
{
throw new UnsupportedOperationException();
}
private boolean removeFromMemory( long element )
{
final int idx = indexOf( element );
final long valueAtIdx = memory.readLong( (long) idx * Long.BYTES );
if ( valueAtIdx != element )
{
return false;
}
copyIfFrozen();
memory.writeLong( (long) idx * Long.BYTES, REMOVED );
--elementsInMemory;
++removals;
if ( removals >= resizeRemovalsThreshold )
{
rehashWithoutGrow();
}
return true;
}
private boolean addToMemory( long element )
{
final int idx = indexOf( element );
final long valueAtIdx = valueAt( idx );
if ( valueAtIdx == element )
{
return false;
}
if ( valueAtIdx == REMOVED )
{
--removals;
}
copyIfFrozen();
memory.writeLong( (long) idx * Long.BYTES, element );
++elementsInMemory;
if ( elementsInMemory >= resizeOccupancyThreshold )
{
growAndRehash();
}
return true;
}
@VisibleForTesting
void growAndRehash()
{
final int newCapacity = capacity * 2;
if ( newCapacity < capacity )
{
throw new RuntimeException( "LongSet reached capacity limit" );
}
rehash( newCapacity );
}
@VisibleForTesting
void rehashWithoutGrow()
{
rehash( capacity );
}
private void allocateMemory( int newCapacity )
{
requirePowerOfTwo( newCapacity );
capacity = newCapacity;
resizeOccupancyThreshold = (int) (newCapacity * LOAD_FACTOR);
resizeRemovalsThreshold = newCapacity / REMOVALS_RATIO;
memory = allocator.allocate( (long) newCapacity * Long.BYTES, true, memoryTracker );
}
private void rehash( int newCapacity )
{
final int prevCapacity = capacity;
final Memory prevMemory = memory;
elementsInMemory = 0;
removals = 0;
allocateMemory( newCapacity );
for ( int i = 0; i < prevCapacity; i++ )
{
final long value = prevMemory.readLong( (long) i * Long.BYTES );
if ( isRealValue( value ) )
{
add( value );
}
}
prevMemory.free( memoryTracker );
}
private void copyIfFrozen()
{
if ( frozen )
{
frozen = false;
memory = memory.copy( memoryTracker );
}
}
class FrozenCopy extends AbstractLinearProbeLongHashSet
{
FrozenCopy()
{
super( MutableLinearProbeLongHashSet.this );
}
@Override
public LongSet freeze()
{
return this;
}
@Override
public ImmutableLongSet toImmutable()
{
throw new UnsupportedOperationException();
}
void invalidate()
{
++FrozenCopy.this.modCount;
}
}
}