Skip to content

Commit

Permalink
Clean up after PR review
Browse files Browse the repository at this point in the history
  • Loading branch information
systay committed May 24, 2018
1 parent 001e762 commit 839b5ab
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 102 deletions.
Expand Up @@ -19,15 +19,18 @@
*/
package org.neo4j.cypher.internal.runtime;

import org.neo4j.helpers.collection.Pair;

public class LongArrayHash
{
static final long NOT_IN_USE = -2;
static final int SLOT_EMPTY = 0;
static final int VALUE_FOUND = 1;
static final int CONTINUE_PROBING = -1;

// Static class only
private LongArrayHash()
{
}

public static int hashCode( long[] arr, int from, int numberOfElements )
{
// This way of producing a hashcode for an array of longs is the
Expand Down Expand Up @@ -58,5 +61,4 @@ static boolean validValue( long[] arr, int width )
}
return true;
}

}
Expand Up @@ -38,23 +38,23 @@
*/
public class LongArrayHashMap<VALUE>
{
private final int width;
private final int keySize;
private LongArrayHashTable table;
private Object[] values;

public LongArrayHashMap( int initialCapacity, int width )
public LongArrayHashMap( int initialCapacity, int keySize )
{
assert (initialCapacity & (initialCapacity - 1)) == 0 : "Size must be a power of 2";
assert width > 0 : "Number of elements must be larger than 0";
assert (initialCapacity & (initialCapacity - 1)) == 0 : "Capacity must be a power of 2";
assert keySize > 0 : "Number of elements must be larger than 0";

this.width = width;
table = new LongArrayHashTable( initialCapacity, width );
this.keySize = keySize;
table = new LongArrayHashTable( initialCapacity, keySize );
values = new Object[initialCapacity];
}

public VALUE getOrCreateAndAdd( long[] key, Supplier<VALUE> creator )
public VALUE computeIfAbsent( long[] key, Supplier<VALUE> creator )
{
assert LongArrayHash.validValue( key, width );
assert LongArrayHash.validValue( key, keySize );
int slotNr = slotFor( key );
while ( true )
{
Expand Down Expand Up @@ -96,7 +96,7 @@ public VALUE getOrCreateAndAdd( long[] key, Supplier<VALUE> creator )

public VALUE get( long[] key )
{
assert LongArrayHash.validValue( key, width );
assert LongArrayHash.validValue( key, keySize );
int slotNr = slotFor( key );
while ( true )
{
Expand Down Expand Up @@ -135,7 +135,7 @@ private void resize()

private int slotFor( long[] value )
{
return LongArrayHash.hashCode( value, 0, width ) & table.tableMask;
return LongArrayHash.hashCode( value, 0, keySize ) & table.tableMask;
}

public Iterator<Map.Entry<long[],VALUE>> iterator()
Expand All @@ -148,7 +148,7 @@ public Iterator<Map.Entry<long[],VALUE>> iterator()
protected Map.Entry<long[],VALUE> fetchNextOrNull()
{
// First, find a good spot
while ( current < table.capacity && table.keys[current * width] == NOT_IN_USE )
while ( current < table.capacity && table.keys[current * keySize] == NOT_IN_USE )
{
current = current + 1;
}
Expand All @@ -160,8 +160,8 @@ protected Map.Entry<long[],VALUE> fetchNextOrNull()
}

// Otherwise, let's create the return object.
long[] key = new long[width];
System.arraycopy( table.keys, current * width, key, 0, width );
long[] key = new long[keySize];
System.arraycopy( table.keys, current * keySize, key, 0, keySize );

@SuppressWarnings( "unchecked" )
VALUE value = (VALUE) values[current];
Expand Down Expand Up @@ -201,7 +201,7 @@ public VALUE getValue()
@Override
public VALUE setValue( VALUE value )
{
return null;
throw new UnsupportedOperationException();
}
}
}
Expand Up @@ -35,23 +35,23 @@
*/
public class LongArrayHashMultiMap<VALUE>
{
private final int width;
private final int keySize;
private LongArrayHashTable table;
private Object[] values;

public LongArrayHashMultiMap( int initialCapacity, int width )
public LongArrayHashMultiMap( int initialCapacity, int keySize )
{
assert (initialCapacity & (initialCapacity - 1)) == 0 : "Size must be a power of 2";
assert width > 0 : "Number of elements must be larger than 0";
assert (initialCapacity & (initialCapacity - 1)) == 0 : "Capacity must be a power of 2";
assert keySize > 0 : "Number of elements must be larger than 0";

this.width = width;
table = new LongArrayHashTable( initialCapacity, width );
this.keySize = keySize;
table = new LongArrayHashTable( initialCapacity, keySize );
values = new Object[initialCapacity];
}

public void add( long[] key, VALUE value )
{
assert LongArrayHash.validValue( key, width );
assert LongArrayHash.validValue( key, keySize );
int slotNr = slotFor( key );

while ( true )
Expand All @@ -62,7 +62,7 @@ public void add( long[] key, VALUE value )
case SLOT_EMPTY:
if ( table.timeToResize() )
{
// We know we need to add the value to the set, but there is no space left
// We know we need to add the value to the map, but there is no space left
resize();
// Need to restart linear probe after resizing
slotNr = slotFor( key );
Expand Down Expand Up @@ -95,7 +95,7 @@ public void add( long[] key, VALUE value )

public Iterator<VALUE> get( long[] key )
{
assert LongArrayHash.validValue( key, width );
assert LongArrayHash.validValue( key, keySize );
int slot = slotFor( key );

// Here we'll spin while the slot is taken by a different value.
Expand Down Expand Up @@ -124,7 +124,7 @@ private void resize()

private int slotFor( long[] value )
{
return LongArrayHash.hashCode( value, 0, width ) & table.tableMask;
return LongArrayHash.hashCode( value, 0, keySize ) & table.tableMask;
}

class Node
Expand Down
Expand Up @@ -59,7 +59,7 @@ class LongArrayHashTable
*/
boolean timeToResize()
{
return numberOfEntries == resizeLimit;
return numberOfEntries >= resizeLimit;
}

/***
Expand Down Expand Up @@ -101,20 +101,14 @@ int checkSlot( int slot, long[] key )
void claimSlot( int slot, long[] key )
{
int offset = slot * width;
assert keys[offset] == NOT_IN_USE : "Tried overwriting an already used slot";
System.arraycopy( key, 0, keys, offset, width );
numberOfEntries++;
}

public boolean isEmpty()
{
for ( int i = 0; i < keys.length; i = i + width )
{
if ( keys[i] != NOT_IN_USE )
{
return false;
}
}
return true;
return numberOfEntries == 0;
}

/**
Expand All @@ -136,39 +130,39 @@ private int findUnusedSlot( int fromSlot )

LongArrayHashTable doubleCapacity()
{
LongArrayHashTable newTable = new LongArrayHashTable( capacity * 2, width );
newTable.numberOfEntries = numberOfEntries;
LongArrayHashTable toTable = new LongArrayHashTable( capacity * 2, width );
toTable.numberOfEntries = numberOfEntries;

for ( int fromOffset = 0; fromOffset < capacity * width; fromOffset = fromOffset + width )
{
if ( keys[fromOffset] != NOT_IN_USE )
{
int toSlot = LongArrayHash.hashCode( keys, fromOffset, width ) & newTable.tableMask;
toSlot = newTable.findUnusedSlot( toSlot );
System.arraycopy( keys, fromOffset, newTable.keys, toSlot * width, width );
int toSlot = LongArrayHash.hashCode( keys, fromOffset, width ) & toTable.tableMask;
toSlot = toTable.findUnusedSlot( toSlot );
System.arraycopy( keys, fromOffset, toTable.keys, toSlot * width, width );
}
}

return newTable;
return toTable;
}

Pair<LongArrayHashTable,Object[]> doubleCapacity( Object[] srcValues )
Pair<LongArrayHashTable,Object[]> doubleCapacity( Object[] fromValues )
{
LongArrayHashTable dstTable = new LongArrayHashTable( capacity * 2, width );
Object[] dstValues = new Object[capacity * 2];
long[] srcKeys = keys;
dstTable.numberOfEntries = numberOfEntries;
LongArrayHashTable toTable = new LongArrayHashTable( capacity * 2, width );
Object[] toValues = new Object[capacity * 2];
long[] fromKeys = keys;
toTable.numberOfEntries = numberOfEntries;
for ( int fromSlot = 0; fromSlot < capacity; fromSlot = fromSlot + 1 )
{
int fromOffset = fromSlot * width;
if ( srcKeys[fromOffset] != NOT_IN_USE )
if ( fromKeys[fromOffset] != NOT_IN_USE )
{
int toSlot = LongArrayHash.hashCode( srcKeys, fromOffset, width ) & dstTable.tableMask;
toSlot = dstTable.findUnusedSlot( toSlot );
System.arraycopy( srcKeys, fromOffset, dstTable.keys, toSlot * width, width );
dstValues[toSlot] = srcValues[fromSlot];
int toSlot = LongArrayHash.hashCode( fromKeys, fromOffset, width ) & toTable.tableMask;
toSlot = toTable.findUnusedSlot( toSlot );
System.arraycopy( fromKeys, fromOffset, toTable.keys, toSlot * width, width );
toValues[toSlot] = fromValues[fromSlot];
}
}
return Pair.of( dstTable, dstValues );
return Pair.of( toTable, toValues );
}
}
Expand Up @@ -24,12 +24,14 @@ import java.util.function.Supplier
import org.scalatest.{FunSuite, Matchers}

import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.util.Random

class LongArrayHashMapTest extends FunSuite with Matchers {
class LongArrayHashMapTest extends FunSuite with Matchers with RandomTester {
test("basic") {
val map = new LongArrayHashMap[String](32, 3)
map.getOrCreateAndAdd(Array(1L, 2L, 3L), () => "hello") should equal("hello")
map.getOrCreateAndAdd(Array(1L, 2L, 3L), () => "world") should equal("hello")
map.computeIfAbsent(Array(1L, 2L, 3L), () => "hello") should equal("hello")
map.computeIfAbsent(Array(1L, 2L, 3L), () => "world") should equal("hello")

map.get(Array(1L, 2L, 3L)) should equal("hello")
resultAsSet(map) should equal(Set(List(1L, 2L, 3L) -> "hello"))
Expand All @@ -44,14 +46,14 @@ class LongArrayHashMapTest extends FunSuite with Matchers {

test("fill and doubleCapacity") {
val map = new LongArrayHashMap[String](8, 3)
map.getOrCreateAndAdd(Array(0L, 8L, 1L), () => "hello")
map.getOrCreateAndAdd(Array(0L, 7L, 2L), () => "is")
map.getOrCreateAndAdd(Array(0L, 6L, 3L), () => "it")
map.getOrCreateAndAdd(Array(0L, 5L, 4L), () => "me")
map.getOrCreateAndAdd(Array(0L, 4L, 5L), () => "you")
map.getOrCreateAndAdd(Array(0L, 3L, 6L), () => "are")
map.getOrCreateAndAdd(Array(0L, 2L, 7L), () => "looking")
map.getOrCreateAndAdd(Array(0L, 1L, 8L), () => "for")
map.computeIfAbsent(Array(0L, 8L, 1L), () => "hello")
map.computeIfAbsent(Array(0L, 7L, 2L), () => "is")
map.computeIfAbsent(Array(0L, 6L, 3L), () => "it")
map.computeIfAbsent(Array(0L, 5L, 4L), () => "me")
map.computeIfAbsent(Array(0L, 4L, 5L), () => "you")
map.computeIfAbsent(Array(0L, 3L, 6L), () => "are")
map.computeIfAbsent(Array(0L, 2L, 7L), () => "looking")
map.computeIfAbsent(Array(0L, 1L, 8L), () => "for")

map.get(Array(0L, 8L, 1L)) should equal("hello")
map.get(Array(0L, 7L, 2L)) should equal("is")
Expand Down Expand Up @@ -85,4 +87,61 @@ class LongArrayHashMapTest extends FunSuite with Matchers {
override def get(): T = f()
}

randomTest { randomer =>
val r = randomer.r
val width = r.nextInt(10) + 2
val size = r.nextInt(10000)
val tested = new LongArrayHashMap[String](16, width)
val validator = new mutable.HashMap[Array[Long], String]()
(0 to size) foreach { _ =>
val key = new Array[Long](width)
(0 until width) foreach { i => key(i) = randomer.randomLong() }
tested.computeIfAbsent(key, () => key.toString)
validator.getOrElseUpdate(key, key.toString)
}

validator foreach { case (key: Array[Long], expectedValue: String) =>
val v = tested.get(key)
v should equal(expectedValue)
}

(0 to size) foreach { _ =>
val tuple = new Array[Long](width)
(0 until width) foreach { i => tuple(i) = randomer.randomLong() }
val a = tested.get(tuple)
val b = validator.getOrElse(tuple, null)
a should equal(b)
}

}
}

trait RandomTester {
self: FunSuite =>
def randomTest(f: Randomer => Unit): Unit = {
val seed = System.nanoTime()
val rand = new Random(seed)
val input = new Randomer {
override val r: Random = rand
}

(0 to 100) foreach { i =>
test(s"random test with seed $seed uniquefier $i") {
f(input)
}
}
}

trait Randomer {
val r: Random

def randomLong(): Long = {
val x = r.nextLong()
if (x == -1 || x == -2)
randomLong()
else
x
}
}

}

0 comments on commit 839b5ab

Please sign in to comment.