From 8e32ca031dfabb380b1124e73263aeca7a04b84a Mon Sep 17 00:00:00 2001 From: Andrei Koval Date: Mon, 2 Jul 2018 16:04:27 +0200 Subject: [PATCH] Implement offheap LongLongMap --- .../offheap/LinearProbeLongLongHashMap.java | 1021 +++++++++++++++++ .../LinearProbeLongLongHashMapTest.java | 717 ++++++++++++ .../MutableLinearProbeLongHashSetTest.java | 118 +- 3 files changed, 1758 insertions(+), 98 deletions(-) create mode 100644 community/collections/src/main/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMap.java create mode 100644 community/collections/src/test/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMapTest.java diff --git a/community/collections/src/main/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMap.java b/community/collections/src/main/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMap.java new file mode 100644 index 0000000000000..0398ff293e29e --- /dev/null +++ b/community/collections/src/main/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMap.java @@ -0,0 +1,1021 @@ +/* + * Copyright (c) 2002-2018 "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.collection.offheap; + +import org.eclipse.collections.api.LazyLongIterable; +import org.eclipse.collections.api.LongIterable; +import org.eclipse.collections.api.RichIterable; +import org.eclipse.collections.api.bag.MutableBag; +import org.eclipse.collections.api.bag.primitive.MutableLongBag; +import org.eclipse.collections.api.block.function.primitive.LongFunction; +import org.eclipse.collections.api.block.function.primitive.LongFunction0; +import org.eclipse.collections.api.block.function.primitive.LongToLongFunction; +import org.eclipse.collections.api.block.function.primitive.LongToObjectFunction; +import org.eclipse.collections.api.block.function.primitive.ObjectLongToObjectFunction; +import org.eclipse.collections.api.block.predicate.primitive.LongLongPredicate; +import org.eclipse.collections.api.block.predicate.primitive.LongPredicate; +import org.eclipse.collections.api.block.procedure.Procedure; +import org.eclipse.collections.api.block.procedure.Procedure2; +import org.eclipse.collections.api.block.procedure.primitive.LongLongProcedure; +import org.eclipse.collections.api.block.procedure.primitive.LongProcedure; +import org.eclipse.collections.api.block.procedure.primitive.ObjectIntProcedure; +import org.eclipse.collections.api.collection.primitive.MutableLongCollection; +import org.eclipse.collections.api.iterator.MutableLongIterator; +import org.eclipse.collections.api.map.primitive.ImmutableLongLongMap; +import org.eclipse.collections.api.map.primitive.LongLongMap; +import org.eclipse.collections.api.map.primitive.MutableLongLongMap; +import org.eclipse.collections.api.set.primitive.MutableLongSet; +import org.eclipse.collections.api.tuple.primitive.LongLongPair; +import org.eclipse.collections.impl.SpreadFunctions; +import org.eclipse.collections.impl.lazy.AbstractLazyIterable; +import org.eclipse.collections.impl.map.mutable.primitive.SynchronizedLongLongMap; +import org.eclipse.collections.impl.map.mutable.primitive.UnmodifiableLongLongMap; +import org.eclipse.collections.impl.primitive.AbstractLongIterable; + +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.neo4j.graphdb.Resource; +import org.neo4j.util.VisibleForTesting; + +import static java.lang.Integer.bitCount; +import static java.util.Objects.requireNonNull; +import static org.eclipse.collections.impl.tuple.primitive.PrimitiveTuples.pair; +import static org.neo4j.util.Preconditions.checkArgument; + +/** + * Off heap implementation of long-long hash map. + * + */ +class LinearProbeLongLongHashMap extends AbstractLongIterable implements MutableLongLongMap, Resource +{ + @VisibleForTesting + static final int DEFAULT_CAPACITY = 32; + @VisibleForTesting + static final double REMOVALS_FACTOR = 0.25; + private static final double LOAD_FACTOR = 0.75; + + private static final long EMPTY_KEY = 0; + private static final long REMOVED_KEY = 1; + private static final long EMPTY_VALUE = 0; + private static final long ENTRY_SIZE = 2 * Long.BYTES; + + private final MemoryAllocator allocator; + + private Memory memory; + private int capacity; + private long modCount; + private int resizeOccupancyThreshold; + private int resizeRemovalsThreshold; + private int removals; + private int entriesInMemory; + + private boolean hasZeroKey; + private boolean hasOneKey; + private long zeroValue; + private long oneValue; + + LinearProbeLongLongHashMap( MemoryAllocator allocator ) + { + this.allocator = requireNonNull( allocator ); + allocateMemory( DEFAULT_CAPACITY ); + } + + @Override + public void put( long key, long value ) + { + ++modCount; + + if ( isSentinelKey( key ) ) + { + putForSentinelKey( key, value ); + return; + } + + final int idx = indexOf( key ); + final long keyAtIdx = getKeyAt( idx ); + + if ( keyAtIdx == key ) + { + setValueAt( idx, value ); + return; + } + + if ( keyAtIdx == REMOVED_KEY ) + { + --removals; + } + + setKeyAt( idx, key ); + setValueAt( idx, value ); + + ++entriesInMemory; + if ( entriesInMemory >= resizeOccupancyThreshold ) + { + growAndRehash(); + } + } + + @Override + public void putAll( LongLongMap map ) + { + ++modCount; + map.forEachKeyValue( this::put ); + } + + /** + * @param key + * @return value associated with the key, or {@code zero} if the map doesn't conain this key + */ + @Override + public long get( long key ) + { + return getIfAbsent( key, EMPTY_VALUE ); + } + + @Override + public long getIfAbsent( long key, long ifAbsent ) + { + if ( isSentinelKey( key ) ) + { + return getForSentinelKey( key, ifAbsent ); + } + + final int idx = indexOf( key ); + final long keyAtIdx = getKeyAt( idx ); + + if ( keyAtIdx == key ) + { + return getValueAt( idx ); + } + + return ifAbsent; + } + + @Override + public long getIfAbsentPut( long key, long value ) + { + return getIfAbsentPut( key, () -> value ); + } + + @Override + public long getIfAbsentPut( long key, LongFunction0 supplier ) + { + if ( isSentinelKey( key ) ) + { + return getIfAbsentPutForSentinelKey( key, supplier ); + } + + final int idx = indexOf( key ); + final long keyAtIdx = getKeyAt( idx ); + + if ( keyAtIdx == key ) + { + return getValueAt( idx ); + } + + ++modCount; + final long value = supplier.value(); + + if ( keyAtIdx == REMOVED_KEY ) + { + --removals; + } + + setKeyAt( idx, key ); + setValueAt( idx, value ); + + ++entriesInMemory; + if ( entriesInMemory >= resizeOccupancyThreshold ) + { + growAndRehash(); + } + + return value; + } + + @Override + public long getIfAbsentPutWithKey( long key, LongToLongFunction function ) + { + return getIfAbsentPut( key, () -> function.valueOf( key ) ); + } + + @Override + public

long getIfAbsentPutWith( long key, LongFunction function, P parameter ) + { + return getIfAbsentPut( key, () -> function.longValueOf( parameter ) ); + } + + @Override + public long getOrThrow( long key ) + { + return getIfAbsentPut( key, () -> + { + throw new IllegalStateException( "Key not found: " + key ); + } ); + } + + @Override + public void removeKey( long key ) + { + removeKeyIfAbsent( key, EMPTY_VALUE ); + } + + @Override + public void remove( long key ) + { + removeKeyIfAbsent( key, EMPTY_VALUE ); + } + + @Override + public long removeKeyIfAbsent( long key, long ifAbsent ) + { + ++modCount; + + if ( isSentinelKey( key ) ) + { + return removeForSentinelKey( key, ifAbsent ); + } + + final int idx = indexOf( key ); + final long keyAtIdx = getKeyAt( idx ); + + if ( keyAtIdx != key ) + { + return ifAbsent; + } + + setKeyAt( idx, REMOVED_KEY ); + --entriesInMemory; + ++removals; + + final long oldValue = getValueAt( idx ); + + if ( removals >= resizeRemovalsThreshold ) + { + rehashWithoutGrow(); + } + + return oldValue; + } + + @Override + public boolean containsKey( long key ) + { + if ( isSentinelKey( key ) ) + { + return (key == EMPTY_KEY && hasZeroKey) || (key == REMOVED_KEY && hasOneKey); + } + + final int idx = indexOf( key ); + final long keyAtIdx = getKeyAt( idx ); + return key == keyAtIdx; + } + + @Override + public boolean containsValue( long value ) + { + if ( hasZeroKey && zeroValue == value ) + { + return true; + } + if ( hasOneKey && oneValue == value ) + { + return true; + } + + for ( int i = 0; i < capacity; i++ ) + { + final long key = getKeyAt( i ); + if ( !isSentinelKey( key ) && getValueAt( i ) == value ) + { + return true; + } + } + + return false; + } + + @Override + public long updateValue( long key, long initialValueIfAbsent, LongToLongFunction function ) + { + ++modCount; + + if ( isSentinelKey( key ) ) + { + return updateValueForSentinelKey( key, initialValueIfAbsent, function ); + } + + final int idx = indexOf( key ); + final long keyAtIdx = getKeyAt( idx ); + + if ( keyAtIdx == key ) + { + final long newValue = function.applyAsLong( getValueAt( idx ) ); + setValueAt( idx, newValue ); + return newValue; + } + + if ( keyAtIdx == REMOVED_KEY ) + { + --removals; + } + + final long value = function.applyAsLong( initialValueIfAbsent ); + + setKeyAt( idx, key ); + setValueAt( idx, value ); + + ++entriesInMemory; + if ( entriesInMemory >= resizeOccupancyThreshold ) + { + growAndRehash(); + } + + return value; + } + + @Override + public long addToValue( long key, long toBeAdded ) + { + return updateValue( key, 0, v -> v + toBeAdded ); + } + + @Override + public void forEachKey( LongProcedure procedure ) + { + if ( hasZeroKey ) + { + procedure.value( 0 ); + } + if ( hasOneKey ) + { + procedure.value( 1 ); + } + + int left = entriesInMemory; + for ( int i = 0; i < capacity && left > 0; i++ ) + { + final long key = getKeyAt( i ); + if ( !isSentinelKey( key ) ) + { + procedure.value( key ); + --left; + } + } + } + + @Override + public void forEachValue( LongProcedure procedure ) + { + forEachKeyValue( ( key, value ) -> procedure.value( value ) ); + } + + @Override + public void forEachKeyValue( LongLongProcedure procedure ) + { + if ( hasZeroKey ) + { + procedure.value( 0, zeroValue ); + } + if ( hasOneKey ) + { + procedure.value( 1, oneValue ); + } + + int left = entriesInMemory; + for ( int i = 0; i < capacity && left > 0; i++ ) + { + final long key = getKeyAt( i ); + if ( !isSentinelKey( key ) ) + { + final long value = getValueAt( i ); + procedure.value( key, value ); + --left; + } + } + } + + @Override + public MutableLongCollection values() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void clear() + { + ++modCount; + hasZeroKey = false; + hasOneKey = false; + entriesInMemory = 0; + removals = 0; + memory.free(); + allocateMemory( DEFAULT_CAPACITY ); + } + + @Override + public MutableLongLongMap flipUniqueValues() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public MutableLongLongMap select( LongLongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public MutableLongLongMap reject( LongLongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public MutableLongLongMap withKeyValue( long key, long value ) + { + put( key, value ); + return this; + } + + @Override + public MutableLongLongMap withoutKey( long key ) + { + removeKey( key ); + return this; + } + + @Override + public MutableLongLongMap withoutAllKeys( LongIterable keys ) + { + keys.each( this::removeKey ); + return this; + } + + @Override + public MutableLongLongMap asUnmodifiable() + { + return new UnmodifiableLongLongMap( this ); + } + + @Override + public MutableLongLongMap asSynchronized() + { + return new SynchronizedLongLongMap( this ); + } + + @Override + public LazyLongIterable keysView() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public RichIterable keyValuesView() + { + return new KeyValuesView(); + } + + @Override + public ImmutableLongLongMap toImmutable() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public MutableLongSet keySet() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public MutableLongIterator longIterator() + { + return new KeysIterator(); + } + + @Override + public long[] toArray() + { + final int[] i = {0}; + final long[] array = new long[size()]; + each( element -> array[i[0]++] = element ); + return array; + } + + @Override + public boolean contains( long value ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void forEach( LongProcedure procedure ) + { + each( procedure ); + } + + @Override + public void each( LongProcedure procedure ) + { + if ( hasZeroKey ) + { + procedure.value( 0 ); + } + if ( hasOneKey ) + { + procedure.value( 1 ); + } + + int left = entriesInMemory; + for ( int i = 0; i < capacity && left > 0; i++ ) + { + final long key = getKeyAt( i ); + if ( !isSentinelKey( key ) ) + { + procedure.value( key ); + --left; + } + } + } + + @Override + public MutableLongBag select( LongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public MutableLongBag reject( LongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public MutableBag collect( LongToObjectFunction function ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public long detectIfNone( LongPredicate predicate, long ifNone ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public int count( LongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public boolean anySatisfy( LongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public boolean allSatisfy( LongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public boolean noneSatisfy( LongPredicate predicate ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public T injectInto( T injectedValue, ObjectLongToObjectFunction function ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public long sum() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public long max() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public long min() + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public int size() + { + return entriesInMemory + (hasOneKey ? 1 : 0) + (hasZeroKey ? 1 : 0); + } + + @Override + public void appendString( Appendable appendable, String start, String separator, String end ) + { + throw new UnsupportedOperationException( "not implemented" ); + } + + @Override + public void close() + { + ++modCount; + if ( memory != null ) + { + memory.free(); + memory = null; + } + } + + @VisibleForTesting + void rehashWithoutGrow() + { + rehash( capacity ); + } + + @VisibleForTesting + void growAndRehash() + { + final int newCapacity = capacity * 2; + if ( newCapacity < capacity ) + { + throw new RuntimeException( "Map reached capacity limit" ); + } + rehash( newCapacity ); + } + + @VisibleForTesting + int hashAndMask( long element ) + { + final long h = SpreadFunctions.longSpreadOne( element ); + return Long.hashCode( h ) & (capacity - 1); + } + + int indexOf( long element ) + { + int idx = hashAndMask( element ); + int firstRemovedIdx = -1; + + for ( int i = 0; i < capacity; i++ ) + { + final long keyAtIdx = getKeyAt( idx ); + + if ( keyAtIdx == element ) + { + return idx; + } + + if ( keyAtIdx == EMPTY_KEY ) + { + return firstRemovedIdx == -1 ? idx : firstRemovedIdx; + } + + if ( keyAtIdx == REMOVED_KEY && firstRemovedIdx == -1 ) + { + firstRemovedIdx = idx; + } + + idx = (idx + 1) & (capacity - 1); + } + + throw new AssertionError( "Failed to determine index for " + element ); + } + + private long updateValueForSentinelKey( long key, long initialValueIfAbsent, LongToLongFunction function ) + { + if ( key == EMPTY_KEY ) + { + final long newValue = function.applyAsLong( hasZeroKey ? zeroValue : initialValueIfAbsent ); + hasZeroKey = true; + zeroValue = newValue; + return newValue; + } + if ( key == REMOVED_KEY ) + { + final long newValue = function.applyAsLong( hasOneKey ? oneValue : initialValueIfAbsent ); + hasOneKey = true; + oneValue = newValue; + return newValue; + } + throw new AssertionError( "Invalid sentinel key: " + key ); + } + + private void rehash( int newCapacity ) + { + final int prevCapacity = capacity; + final Memory prevMemory = memory; + entriesInMemory = 0; + removals = 0; + allocateMemory( newCapacity ); + + for ( int i = 0; i < prevCapacity; i++ ) + { + final long key = prevMemory.readLong( i * ENTRY_SIZE ); + if ( !isSentinelKey( key ) ) + { + final long value = prevMemory.readLong( (i * ENTRY_SIZE) + ENTRY_SIZE / 2 ); + put( key, value ); + } + } + + prevMemory.free(); + } + + private static boolean isSentinelKey( long key ) + { + return key == EMPTY_KEY || key == REMOVED_KEY; + } + + private void allocateMemory( int newCapacity ) + { + checkArgument( newCapacity > 1 && bitCount( newCapacity ) == 1, "Capacity must be power of 2" ); + capacity = newCapacity; + resizeOccupancyThreshold = (int) (newCapacity * LOAD_FACTOR); + resizeRemovalsThreshold = (int) (newCapacity * REMOVALS_FACTOR); + memory = allocator.allocate( newCapacity * ENTRY_SIZE ); + } + + private long removeForSentinelKey( long key, long ifAbsent ) + { + if ( key == EMPTY_KEY ) + { + final long result = hasZeroKey ? zeroValue : ifAbsent; + hasZeroKey = false; + return result; + } + if ( key == REMOVED_KEY ) + { + final long result = hasOneKey ? oneValue : ifAbsent; + hasOneKey = false; + return result; + } + throw new AssertionError( "Invalid sentinel key: " + key ); + } + + private long getForSentinelKey( long key, long ifAbsent ) + { + if ( key == EMPTY_KEY ) + { + return hasZeroKey ? zeroValue : ifAbsent; + } + if ( key == REMOVED_KEY ) + { + return hasOneKey ? oneValue : ifAbsent; + } + throw new AssertionError( "Invalid sentinel key: " + key ); + } + + private long getIfAbsentPutForSentinelKey( long key, LongFunction0 supplier ) + { + if ( key == EMPTY_KEY ) + { + if ( !hasZeroKey ) + { + ++modCount; + hasZeroKey = true; + zeroValue = supplier.value(); + } + return zeroValue; + } + if ( key == REMOVED_KEY ) + { + if ( !hasOneKey ) + { + ++modCount; + hasOneKey = true; + oneValue = supplier.value(); + } + return oneValue; + } + throw new AssertionError( "Invalid sentinel key: " + key ); + } + + private void setKeyAt( int idx, long key ) + { + memory.writeLong( idx * ENTRY_SIZE, key ); + } + + private long getKeyAt( int idx ) + { + return memory.readLong( idx * ENTRY_SIZE ); + } + + private void setValueAt( int idx, long value ) + { + memory.writeLong( (idx * ENTRY_SIZE) + ENTRY_SIZE / 2, value ); + } + + private long getValueAt( int idx ) + { + return memory.readLong( (idx * ENTRY_SIZE) + ENTRY_SIZE / 2 ); + } + + private void putForSentinelKey( long key, long value ) + { + if ( key == EMPTY_KEY ) + { + hasZeroKey = true; + zeroValue = value; + } + else if ( key == REMOVED_KEY ) + { + hasOneKey = true; + oneValue = value; + } + else + { + throw new AssertionError( "Invalid sentinel key: " + key ); + } + } + + private class KeyValuesView extends AbstractLazyIterable + { + @Override + public void each( Procedure procedure ) + { + throw new UnsupportedOperationException(); + } + + @Override + public void forEachWithIndex( ObjectIntProcedure objectIntProcedure ) + { + throw new UnsupportedOperationException(); + } + + @Override + public

void forEachWith( Procedure2 procedure, P parameter ) + { + throw new UnsupportedOperationException(); + } + + @Override + public Iterator iterator() + { + return new KeyValuesIterator(); + } + } + + private class KeyValuesIterator implements Iterator + { + private final long modCount = LinearProbeLongLongHashMap.this.modCount; + private int visited; + private int idx; + + private boolean handledZero; + private boolean handledOne; + + @Override + public LongLongPair next() + { + if ( !hasNext() ) + { + throw new NoSuchElementException( "iterator is exhausted" ); + } + + ++visited; + + if ( !handledZero ) + { + handledZero = true; + if ( hasZeroKey ) + { + return pair( 0L, zeroValue ); + } + } + + if ( !handledOne ) + { + handledOne = true; + if ( hasOneKey ) + { + return pair( 1L, oneValue ); + } + } + + long key = getKeyAt( idx ); + while ( isSentinelKey( key ) ) + { + ++idx; + key = getKeyAt( idx ); + } + + final long value = getValueAt( idx ); + ++idx; + return pair( key, value ); + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasNext() + { + validateIteratorState( modCount ); + return visited != size(); + } + } + + private class KeysIterator implements MutableLongIterator + { + private final long modCount = LinearProbeLongLongHashMap.this.modCount; + private int visited; + private int idx; + + private boolean handledZero; + private boolean handledOne; + + @Override + public long next() + { + if ( !hasNext() ) + { + throw new NoSuchElementException( "iterator is exhausted" ); + } + + ++visited; + + if ( !handledZero ) + { + handledZero = true; + if ( hasZeroKey ) + { + return 0L; + } + } + + if ( !handledOne ) + { + handledOne = true; + if ( hasOneKey ) + { + return 1L; + } + } + + long key = getKeyAt( idx ); + while ( isSentinelKey( key ) ) + { + ++idx; + key = getKeyAt( idx ); + } + + ++idx; + return key; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasNext() + { + validateIteratorState( modCount ); + return visited < size(); + } + } + + private void validateIteratorState( long iteratorModCount ) + { + if ( iteratorModCount != modCount ) + { + throw new ConcurrentModificationException(); + } + } +} diff --git a/community/collections/src/test/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMapTest.java b/community/collections/src/test/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMapTest.java new file mode 100644 index 0000000000000..61aeeb899d4eb --- /dev/null +++ b/community/collections/src/test/java/org/neo4j/collection/offheap/LinearProbeLongLongHashMapTest.java @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2002-2018 "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.collection.offheap; + +import org.eclipse.collections.api.block.function.primitive.LongFunction; +import org.eclipse.collections.api.block.function.primitive.LongFunction0; +import org.eclipse.collections.api.block.function.primitive.LongToLongFunction; +import org.eclipse.collections.api.block.procedure.primitive.LongLongProcedure; +import org.eclipse.collections.api.block.procedure.primitive.LongProcedure; +import org.eclipse.collections.api.iterator.MutableLongIterator; +import org.eclipse.collections.api.list.primitive.ImmutableLongList; +import org.eclipse.collections.api.list.primitive.MutableLongList; +import org.eclipse.collections.api.map.primitive.LongLongMap; +import org.eclipse.collections.api.map.primitive.MutableLongLongMap; +import org.eclipse.collections.api.set.primitive.LongSet; +import org.eclipse.collections.api.set.primitive.MutableLongSet; +import org.eclipse.collections.api.tuple.primitive.LongLongPair; +import org.eclipse.collections.impl.factory.primitive.LongLists; +import org.eclipse.collections.impl.factory.primitive.LongLongMaps; +import org.eclipse.collections.impl.factory.primitive.LongSets; +import org.eclipse.collections.impl.map.mutable.primitive.LongLongHashMap; +import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.function.Executable; + +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.function.Consumer; + +import static java.util.Arrays.asList; +import static org.eclipse.collections.impl.list.mutable.primitive.LongArrayList.newListWith; +import static org.eclipse.collections.impl.tuple.primitive.PrimitiveTuples.pair; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.DynamicTest.dynamicTest; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.neo4j.collection.offheap.LinearProbeLongLongHashMap.DEFAULT_CAPACITY; +import static org.neo4j.collection.offheap.LinearProbeLongLongHashMap.REMOVALS_FACTOR; + +class LinearProbeLongLongHashMapTest +{ + private final TestMemoryAllocator allocator = new TestMemoryAllocator(); + private LinearProbeLongLongHashMap map = newMap(); + + private LinearProbeLongLongHashMap newMap() + { + return new LinearProbeLongLongHashMap( allocator ); + } + + @AfterEach + void tearDown() + { + map.close(); + assertEquals( 0, allocator.tracker.usedDirectMemory(), "Leaking memory" ); + } + + @Test + void putGetRemove() + { + map.put( 0, 10 ); + map.put( 1, 11 ); + map.put( 2, 12 ); + + assertEquals( 10, map.get( 0 ) ); + assertEquals( 11, map.get( 1 ) ); + assertEquals( 12, map.get( 2 ) ); + // default empty value + assertEquals( 0, map.get( 3 ) ); + + map.remove( 1 ); + map.remove( 2 ); + map.remove( 0 ); + + assertEquals( 0, map.get( 0 ) ); + assertEquals( 0, map.get( 1 ) ); + assertEquals( 0, map.get( 2 ) ); + } + + @Test + void putAll() + { + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + assertEquals( 3, map.size() ); + assertEquals( 10, map.get( 0 ) ); + assertEquals( 11, map.get( 1 ) ); + assertEquals( 12, map.get( 2 ) ); + } + + @Test + void getIfAbsent() + { + assertEquals( -1, map.getIfAbsent( 0, -1 ) ); + assertEquals( -1, map.getIfAbsent( 1, -1 ) ); + assertEquals( -1, map.getIfAbsent( 2, -1 ) ); + assertEquals( -1, map.getIfAbsent( 3, -1 ) ); + + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + + assertEquals( 10, map.getIfAbsent( 0, -1 ) ); + assertEquals( 11, map.getIfAbsent( 1, -1 ) ); + assertEquals( 12, map.getIfAbsent( 2, -1 ) ); + assertEquals( -1, map.getIfAbsent( 3, -1 ) ); + } + + @Test + void getIfAbsentPut() + { + assertEquals( 10, map.getIfAbsentPut( 0, 10 ) ); + assertEquals( 10, map.getIfAbsentPut( 0, 100 ) ); + assertEquals( 11, map.getIfAbsentPut( 1, 11 ) ); + assertEquals( 11, map.getIfAbsentPut( 1, 110 ) ); + assertEquals( 12, map.getIfAbsentPut( 2, 12 ) ); + assertEquals( 12, map.getIfAbsentPut( 2, 120 ) ); + } + + @Test + void getIfAbsentPut_Supplier() + { + final LongFunction0 supplier = mock( LongFunction0.class ); + doReturn( 10L, 11L, 12L ).when( supplier ).value(); + + assertEquals( 10, map.getIfAbsentPut( 0, supplier ) ); + assertEquals( 11, map.getIfAbsentPut( 1, supplier ) ); + assertEquals( 12, map.getIfAbsentPut( 2, supplier ) ); + verify( supplier, times( 3 ) ).value(); + + assertEquals( 10, map.getIfAbsentPut( 0, supplier ) ); + assertEquals( 11, map.getIfAbsentPut( 1, supplier ) ); + assertEquals( 12, map.getIfAbsentPut( 2, supplier ) ); + verifyNoMoreInteractions( supplier ); + } + + @Test + void getIfAbsentPutWithKey() + { + @SuppressWarnings( "Convert2Lambda" ) + final LongToLongFunction function = spy( new LongToLongFunction() + { + @Override + public long valueOf( long x ) + { + return 10 + x; + } + } ); + + assertEquals( 10, map.getIfAbsentPutWithKey( 0, function ) ); + assertEquals( 10, map.getIfAbsentPutWithKey( 0, function ) ); + assertEquals( 11, map.getIfAbsentPutWithKey( 1, function ) ); + assertEquals( 11, map.getIfAbsentPutWithKey( 1, function ) ); + assertEquals( 12, map.getIfAbsentPutWithKey( 2, function ) ); + assertEquals( 12, map.getIfAbsentPutWithKey( 2, function ) ); + + verify( function ).valueOf( eq( 0L ) ); + verify( function ).valueOf( eq( 1L ) ); + verify( function ).valueOf( eq( 2L ) ); + verifyNoMoreInteractions( function ); + } + + @Test + void getIfAbsentPutWith() + { + @SuppressWarnings( {"Convert2Lambda", "Anonymous2MethodRef"} ) + final LongFunction function = spy( new LongFunction() + { + + @Override + public long longValueOf( String s ) + { + return Long.valueOf( s ); + } + } ); + + assertEquals( 10, map.getIfAbsentPutWith( 0, function, "10" ) ); + assertEquals( 10, map.getIfAbsentPutWith( 0, function, "10" ) ); + assertEquals( 11, map.getIfAbsentPutWith( 1, function, "11" ) ); + assertEquals( 11, map.getIfAbsentPutWith( 1, function, "11" ) ); + assertEquals( 12, map.getIfAbsentPutWith( 2, function, "12" ) ); + assertEquals( 12, map.getIfAbsentPutWith( 2, function, "12" ) ); + + verify( function ).longValueOf( eq( "10" ) ); + verify( function ).longValueOf( eq( "11" ) ); + verify( function ).longValueOf( eq( "12" ) ); + verifyNoMoreInteractions( function ); + } + + @Test + void getOrThrow() + { + assertThrows( IllegalStateException.class, () -> map.getOrThrow( 0 ) ); + assertThrows( IllegalStateException.class, () -> map.getOrThrow( 1 ) ); + assertThrows( IllegalStateException.class, () -> map.getOrThrow( 2 ) ); + + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + + assertEquals( 10, map.getOrThrow( 0 ) ); + assertEquals( 11, map.getOrThrow( 1 ) ); + assertEquals( 12, map.getOrThrow( 2 ) ); + } + + @Test + void putOverwrite() + { + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + + assertEquals( 10, map.get( 0 ) ); + assertEquals( 11, map.get( 1 ) ); + assertEquals( 12, map.get( 2 ) ); + + map.putAll( LongLongHashMap.newWithKeysValues( 0, 20, 1, 21, 2, 22 ) ); + + assertEquals( 20, map.get( 0 ) ); + assertEquals( 21, map.get( 1 ) ); + assertEquals( 22, map.get( 2 ) ); + } + + @Test + void size() + { + assertEquals( 0, map.size() ); + map.put( 0, 10 ); + assertEquals( 1, map.size() ); + map.put( 1, 11 ); + assertEquals( 2, map.size() ); + map.put( 2, 12 ); + assertEquals( 3, map.size() ); + map.put( 0, 20 ); + map.put( 1, 20 ); + map.put( 2, 20 ); + assertEquals( 3, map.size() ); + map.remove( 0 ); + assertEquals( 2, map.size() ); + map.remove( 1 ); + assertEquals( 1, map.size() ); + map.remove( 2 ); + assertEquals( 0, map.size() ); + } + + @Test + void containsKey() + { + assertFalse( map.containsKey( 0 ) ); + assertFalse( map.containsKey( 1 ) ); + assertFalse( map.containsKey( 2 ) ); + + map.put( 0, 10 ); + assertTrue( map.containsKey( 0 ) ); + map.put( 1, 11 ); + assertTrue( map.containsKey( 1 ) ); + map.put( 2, 12 ); + assertTrue( map.containsKey( 2 ) ); + + map.remove( 0 ); + assertFalse( map.containsKey( 0 ) ); + map.remove( 1 ); + assertFalse( map.containsKey( 1 ) ); + map.remove( 2 ); + assertFalse( map.containsKey( 2 ) ); + } + + @Test + void containsValue() + { + assertFalse( map.containsValue( 10 ) ); + assertFalse( map.containsValue( 11 ) ); + assertFalse( map.containsValue( 12 ) ); + + map.put( 0, 10 ); + assertTrue( map.containsValue( 10 ) ); + + map.put( 1, 11 ); + assertTrue( map.containsValue( 11 ) ); + + map.put( 2, 12 ); + assertTrue( map.containsValue( 12 ) ); + } + + @Test + void removeKeyIfAbsent() + { + assertEquals( 10, map.removeKeyIfAbsent( 0, 10 ) ); + assertEquals( 11, map.removeKeyIfAbsent( 1, 11 ) ); + assertEquals( 12, map.removeKeyIfAbsent( 2, 12 ) ); + + map.put( 0, 10 ); + map.put( 1, 11 ); + map.put( 2, 12 ); + + assertEquals( 10, map.removeKeyIfAbsent( 0, -1 ) ); + assertEquals( 11, map.removeKeyIfAbsent( 1, -1 ) ); + assertEquals( 12, map.removeKeyIfAbsent( 2, -1 ) ); + + assertEquals( 0, map.size() ); + } + + @Test + void updateValue() + { + map.updateValue( 0, 10, v -> -v ); + map.updateValue( 1, 11, v -> -v ); + map.updateValue( 2, 12, v -> -v ); + + assertEquals( -10, map.get( 0 ) ); + assertEquals( -11, map.get( 1 ) ); + assertEquals( -12, map.get( 2 ) ); + + map.updateValue( 0, 0, v -> -v ); + map.updateValue( 1, 0, v -> -v ); + map.updateValue( 2, 0, v -> -v ); + + assertEquals( 10, map.get( 0 ) ); + assertEquals( 11, map.get( 1 ) ); + assertEquals( 12, map.get( 2 ) ); + + assertEquals( 3, map.size() ); + } + + @Test + void addToValue() + { + assertEquals( 10, map.addToValue( 0, 10 ) ); + assertEquals( 11, map.addToValue( 1, 11 ) ); + assertEquals( 12, map.addToValue( 2, 12 ) ); + + assertEquals( 110, map.addToValue( 0, 100 ) ); + assertEquals( 111, map.addToValue( 1, 100 ) ); + assertEquals( 112, map.addToValue( 2, 100 ) ); + + assertEquals( 3, map.size() ); + } + + @Test + void forEachKey() + { + final LongProcedure consumer = mock( LongProcedure.class ); + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + + map.forEachKey( consumer ); + + verify( consumer ).value( eq( 0L ) ); + verify( consumer ).value( eq( 1L ) ); + verify( consumer ).value( eq( 2L ) ); + verifyNoMoreInteractions( consumer ); + } + + @Test + void forEachValue() + { + final LongProcedure consumer = mock( LongProcedure.class ); + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + + map.forEachValue( consumer ); + + verify( consumer ).value( eq( 10L ) ); + verify( consumer ).value( eq( 11L ) ); + verify( consumer ).value( eq( 12L ) ); + verifyNoMoreInteractions( consumer ); + } + + @Test + void forEachKeyValue() + { + final LongLongProcedure consumer = mock( LongLongProcedure.class ); + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + + map.forEachKeyValue( consumer ); + + verify( consumer ).value( eq( 0L ), eq( 10L ) ); + verify( consumer ).value( eq( 1L ), eq( 11L ) ); + verify( consumer ).value( eq( 2L ), eq( 12L ) ); + verifyNoMoreInteractions( consumer ); + } + + @Test + void clear() + { + map.clear(); + assertEquals( 0, map.size() ); + + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + assertEquals( 3, map.size() ); + + map.clear(); + assertEquals( 0, map.size() ); + + map.clear(); + assertEquals( 0, map.size() ); + } + + @Test + void toList() + { + assertEquals( 0, map.toList().size() ); + + map.putAll( toMap( 0, 1, 2, 3, 4, 5 ) ); + assertEquals( newListWith( 0, 1, 2, 3, 4, 5 ), map.toList().sortThis() ); + } + + @Test + void toArray() + { + assertEquals( 0, map.toArray().length ); + + map.putAll( toMap( 0, 1, 2, 3, 4, 5 ) ); + + assertArrayEquals( new long[]{0, 1, 2, 3, 4, 5}, map.toSortedArray() ); + } + + @Test + void keysIterator() + { + final LongSet keys = LongSets.immutable.of( 0L, 1L, 2L, 42L ); + keys.forEach( k -> map.put( k, k * 10 ) ); + + final MutableLongIterator iter = map.longIterator(); + final MutableLongSet found = new LongHashSet(); + while ( iter.hasNext() ) + { + found.add( iter.next() ); + } + + assertEquals( keys, found ); + } + + @Test + void keysIteratorFailsWhenMapIsClosed() + { + map.putAll( LongLongHashMap.newWithKeysValues( 0, 10, 1, 11, 2, 12 ) ); + + final MutableLongIterator iter = map.longIterator(); + + assertTrue( iter.hasNext() ); + assertEquals( 0, iter.next() ); + + map.close(); + + assertThrows( ConcurrentModificationException.class, iter::hasNext ); + assertThrows( ConcurrentModificationException.class, iter::next ); + } + + @TestFactory + Collection failFastIterator() + { + return asList( + testIteratorsFail( "put sentinel", m -> m.put( 0, 42 ), pair( 1L, 10L ) ), + testIteratorsFail( "put", m -> m.put( 4, 40 ), pair( 1L, 10L ) ), + testIteratorsFail( "put all; emtpy source", m -> m.putAll( LongLongMaps.immutable.empty() ), pair( 1L, 10L ) ), + testIteratorsFail( "overwrite sentinel", m -> m.put( 0, 0 ), pair( 0L, 1L ) ), + testIteratorsFail( "overwrite", m -> m.put( 4, 40 ), pair( 4L, 40L ) ), + testIteratorsFail( "remove sentinel", m -> m.remove( 1 ), pair( 1L, 10L ) ), + testIteratorsFail( "remove", m -> m.remove( 4 ), pair( 4L, 40L ) ), + testIteratorsFail( "remove nonexisting", m -> m.remove( 13 ), pair( 4L, 40L ) ), + testIteratorsFail( "getIfAbsentPut", m -> m.getIfAbsentPut( 10, 100 ), pair( 4L, 40L ) ), + testIteratorsFail( "close", LinearProbeLongLongHashMap::close, pair( 1L, 10L ) ) + ); + } + + @Test + void grow() + { + map = spy( map ); + + for ( int i = 0; i < DEFAULT_CAPACITY; i++ ) + { + map.put( 100 + i, i ); + } + verify( map ).growAndRehash(); + } + + @Test + void rehashWhenTooManyRemovals() + { + map = spy( map ); + + final int numOfElements = DEFAULT_CAPACITY / 2; + final int removalsToTriggerRehashing = (int) (DEFAULT_CAPACITY * REMOVALS_FACTOR); + + for ( int i = 0; i < numOfElements; i++ ) + { + map.put( 100 + i, i ); + } + + assertEquals( numOfElements, map.size() ); + verify( map, never() ).rehashWithoutGrow(); + verify( map, never() ).growAndRehash(); + + for ( int i = 0; i < removalsToTriggerRehashing; i++ ) + { + map.remove( 100 + i ); + } + + assertEquals( numOfElements - removalsToTriggerRehashing, map.size() ); + verify( map ).rehashWithoutGrow(); + verify( map, never() ).growAndRehash(); + } + + @TestFactory + Collection collisions() + { + final ImmutableLongList collisions = generateKeyCollisions( 5 ); + final long a = collisions.get( 0 ); + final long b = collisions.get( 1 ); + final long c = collisions.get( 2 ); + final long d = collisions.get( 3 ); + final long e = collisions.get( 4 ); + + return asList( + dynamicTest( "add all", withNewMap( m -> + { + putAll( m, collisions.toArray() ); + assertEquals( collisions, m.toSortedList() ); + } ) ), + dynamicTest( "add all reversed", withNewMap( m -> + { + putAll( m, collisions.toReversed().toArray() ); + assertEquals( collisions.toReversed(), m.toList() ); + } ) ), + dynamicTest( "add all, remove last", withNewMap( m -> + { + putAll( m, collisions.toArray() ); + m.remove( e ); + assertEquals( newListWith( a, b, c, d ), m.toList() ); + } ) ), + dynamicTest( "add all, remove first", withNewMap( m -> + { + putAll( m, collisions.toArray() ); + m.remove( a ); + assertEquals( newListWith( b, c, d, e ), m.toList() ); + } ) ), + dynamicTest( "add all, remove middle", withNewMap( m -> + { + putAll( m, collisions.toArray() ); + m.remove( b ); + m.remove( d ); + assertEquals( newListWith( a, c, e ), m.toList() ); + } ) ), + dynamicTest( "add all, remove middle 2", withNewMap( m -> + { + putAll( m, collisions.toArray() ); + m.remove( a ); + m.remove( c ); + m.remove( e ); + assertEquals( newListWith( b, d ), m.toList() ); + } ) ), + dynamicTest( "add reuses removed head", withNewMap( m -> + { + putAll( m, a, b, c ); + + m.remove( a ); + assertEquals( newListWith( b, c ), m.toList() ); + + m.put( d, 42 ); + assertEquals( newListWith( d, b, c ), m.toList() ); + } ) ), + dynamicTest( "add reuses removed tail", withNewMap( m -> + { + putAll( m, a, b, c ); + + m.remove( c ); + assertEquals( newListWith( a, b ), m.toList() ); + + m.put( d, 42 ); + assertEquals( newListWith( a, b, d ), m.toList() ); + } ) ), + dynamicTest( "add reuses removed middle", withNewMap( m -> + { + putAll( m, a, b, c ); + + m.remove( b ); + assertEquals( newListWith( a, c ), m.toList() ); + + m.put( d, 42 ); + assertEquals( newListWith( a, d, c ), m.toList() ); + } ) ), + dynamicTest( "add reuses removed middle 2", withNewMap( m -> + { + putAll( m, a, b, c, d, e ); + + m.remove( b ); + m.remove( c ); + assertEquals( newListWith( a, d, e ), m.toList() ); + + m.putAll( toMap( c, b ) ); + assertEquals( newListWith( a, c, b, d, e ), m.toList() ); + } ) ), + dynamicTest( "rehashing compacts sparse sentinels", withNewMap( m -> + { + putAll( m, a, b, c, d, e ); + + m.remove( b ); + m.remove( d ); + m.remove( e ); + assertEquals( newListWith( a, c ), m.toList() ); + + putAll( m, b, d, e ); + assertEquals( newListWith( a, b, c, d, e ), m.toList() ); + + m.remove( b ); + m.remove( d ); + m.remove( e ); + assertEquals( newListWith( a, c ), m.toList() ); + + m.rehashWithoutGrow(); + putAll( m, e, d, b ); + assertEquals( newListWith( a, c, e, d, b ), m.toList() ); + } ) ) + ); + } + + private static void putAll( LinearProbeLongLongHashMap m, long... keys ) + { + for ( long key: keys ) + { + m.put( key, System.nanoTime() ); + } + } + + private static LongLongMap toMap( long... keys ) + { + final MutableLongLongMap m = new LongLongHashMap(); + for ( long key: keys ) + { + m.put( key, System.nanoTime() ); + } + return m; + } + + private DynamicTest testIteratorsFail( String name, Consumer mutator, LongLongPair... initialValues ) + { + return dynamicTest( name, withNewMap( m -> + { + for ( LongLongPair pair: initialValues ) + { + m.putPair( pair ); + } + + final MutableLongIterator keysIterator = m.longIterator(); + final Iterator keyValueIterator = m.keyValuesView().iterator(); + + assertTrue( keysIterator.hasNext() ); + assertDoesNotThrow( keysIterator::next ); + assertTrue( keyValueIterator.hasNext() ); + assertDoesNotThrow( keyValueIterator::next ); + + mutator.accept( m ); + + assertThrows( ConcurrentModificationException.class, keysIterator::hasNext ); + assertThrows( ConcurrentModificationException.class, keysIterator::next ); + assertThrows( ConcurrentModificationException.class, keyValueIterator::hasNext ); + assertThrows( ConcurrentModificationException.class, keyValueIterator::next ); + } ) ); + } + + private Executable withNewMap( Consumer test ) + { + return () -> + { + try ( LinearProbeLongLongHashMap m = newMap() ) + { + test.accept( m ); + } + }; + } + + private ImmutableLongList generateKeyCollisions( int n ) + { + long v = 1984; + final MutableLongList elements; + try ( LinearProbeLongLongHashMap m = newMap() ) + { + final int h = m.hashAndMask( v ); + elements = LongLists.mutable.with( v ); + + while ( elements.size() < n ) + { + ++v; + if ( m.hashAndMask( v ) == h ) + { + elements.add( v ); + } + } + } + return elements.toImmutable(); + } +} diff --git a/community/collections/src/test/java/org/neo4j/collection/offheap/MutableLinearProbeLongHashSetTest.java b/community/collections/src/test/java/org/neo4j/collection/offheap/MutableLinearProbeLongHashSetTest.java index 83e4645af7fe4..9965b783eac0c 100644 --- a/community/collections/src/test/java/org/neo4j/collection/offheap/MutableLinearProbeLongHashSetTest.java +++ b/community/collections/src/test/java/org/neo4j/collection/offheap/MutableLinearProbeLongHashSetTest.java @@ -35,17 +35,13 @@ import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.Executable; -import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.NoSuchElementException; import java.util.function.Consumer; -import org.neo4j.memory.LocalMemoryTracker; import org.neo4j.memory.MemoryAllocationTracker; -import static java.lang.Math.toIntExact; import static java.util.Arrays.asList; import static org.eclipse.collections.impl.list.mutable.primitive.LongArrayList.newListWith; import static org.eclipse.collections.impl.set.mutable.primitive.LongHashSet.newSetWith; @@ -498,27 +494,27 @@ Collection collisions() ); } - private static LongSet drain( LongIterator iter1 ) + private static LongSet drain( LongIterator iter ) { final MutableLongSet result = new LongHashSet(); - while ( iter1.hasNext() ) + while ( iter.hasNext() ) { - result.add( iter1.next() ); + result.add( iter.next() ); } return result; } private DynamicTest testIteratorFails( String name, Consumer mutator, long... initialValues ) { - return dynamicTest( name, withNewSet( set -> + return dynamicTest( name, withNewSet( s -> { - set.addAll( initialValues ); + s.addAll( initialValues ); - final MutableLongIterator iterator = set.longIterator(); + final MutableLongIterator iterator = s.longIterator(); assertTrue( iterator.hasNext() ); assertDoesNotThrow( iterator::next ); - mutator.accept( set ); + mutator.accept( s ); assertThrows( ConcurrentModificationException.class, iterator::hasNext ); assertThrows( ConcurrentModificationException.class, iterator::next ); } ) ); @@ -528,14 +524,9 @@ private Executable withNewSet( Consumer test ) { return () -> { - final MutableLinearProbeLongHashSet set = newSet(); - try + try ( MutableLinearProbeLongHashSet set = newSet() ) { - test.accept( set ); - } - finally - { - set.close(); + test.accept( set ); } }; } @@ -543,20 +534,21 @@ private Executable withNewSet( Consumer test ) private ImmutableLongList generateCollisions( int n ) { long v = 1984; - final MutableLinearProbeLongHashSet set = newSet(); - final int h = set.hashAndMask( v ); - final MutableLongList elements = LongLists.mutable.with( v ); - - while ( elements.size() < n ) + final MutableLongList elements; + try ( MutableLinearProbeLongHashSet s = newSet() ) { - ++v; - if ( set.hashAndMask( v ) == h ) + final int h = s.hashAndMask( v ); + elements = LongLists.mutable.with( v ); + + while ( elements.size() < n ) { - elements.add( v ); + ++v; + if ( s.hashAndMask( v ) == h ) + { + elements.add( v ); + } } } - - set.close(); return elements.toImmutable(); } @@ -566,74 +558,4 @@ private MutableLinearProbeLongHashSet newSet( long... elements ) result.addAll( elements ); return result; } - - static class TestMemoryAllocator implements MemoryAllocator - { - final MemoryAllocationTracker tracker; - - TestMemoryAllocator() - { - this( new LocalMemoryTracker() ); - } - - TestMemoryAllocator( MemoryAllocationTracker tracker ) - { - this.tracker = tracker; - } - - @Override - public Memory allocate( long size ) - { - final ByteBuffer buf = ByteBuffer.allocate( toIntExact( size ) ); - return new MemoryImpl( buf ); - } - - class MemoryImpl implements Memory - { - final ByteBuffer buf; - - MemoryImpl( ByteBuffer buf ) - { - this.buf = buf; - tracker.allocated( buf.capacity() ); - } - - @Override - public long readLong( long offset ) - { - return buf.getLong( toIntExact( offset ) ); - } - - @Override - public void writeLong( long offset, long value ) - { - buf.putLong( toIntExact( offset ), value ); - } - - @Override - public void clear() - { - Arrays.fill( buf.array(), (byte) 0 ); - } - - @Override - public long size() - { - return buf.capacity(); - } - - @Override - public void free() - { - tracker.deallocated( buf.capacity() ); - } - - @Override - public Memory copy() - { - ByteBuffer copyBuf = ByteBuffer.wrap( Arrays.copyOf( buf.array(), buf.array().length ) ); - return new MemoryImpl( copyBuf ); - } - } - } }