diff --git a/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java b/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java index 127546a2c..10e20f132 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/VanillaChronicleHash.java @@ -16,8 +16,11 @@ package net.openhft.chronicle.hash.impl; -import net.openhft.chronicle.hash.*; -import net.openhft.chronicle.hash.impl.hashlookup.HashLookup; +import net.openhft.chronicle.hash.ChronicleHash; +import net.openhft.chronicle.hash.ExternalHashQueryContext; +import net.openhft.chronicle.hash.HashEntry; +import net.openhft.chronicle.hash.HashSegmentContext; +import net.openhft.chronicle.hash.impl.stage.entry.HashLookup; import net.openhft.chronicle.hash.impl.util.BuildVersion; import net.openhft.chronicle.hash.serialization.BytesReader; import net.openhft.chronicle.hash.serialization.SizeMarshaller; diff --git a/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/EntryConsumer.java b/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/EntryConsumer.java deleted file mode 100644 index e9c8e2c3c..000000000 --- a/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/EntryConsumer.java +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2015 higherfrequencytrading.com - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License. - * - * 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package net.openhft.chronicle.hash.impl.hashlookup; - -public interface EntryConsumer { - void accept(long key, long value); -} diff --git a/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/HashLookup.java b/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/HashLookup.java deleted file mode 100644 index 46de62019..000000000 --- a/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/HashLookup.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2015 higherfrequencytrading.com - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License. - * - * 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package net.openhft.chronicle.hash.impl.hashlookup; - -import net.openhft.lang.Maths; - -import static net.openhft.lang.MemoryUnit.BITS; -import static net.openhft.lang.MemoryUnit.BYTES; -import static net.openhft.lang.io.NativeBytes.UNSAFE; - -public class HashLookup { - - // to fit 64 bits per slot. - public static final int MAX_SEGMENT_CHUNKS = 1 << 30; - public static final int MAX_SEGMENT_ENTRIES = 1 << 29; - - public static int valueBits(long actualChunksPerSegment) { - return 64 - Long.numberOfLeadingZeros(actualChunksPerSegment - 1L); - } - - public static int keyBits(long entriesPerSegment, int valueBits) { - // key hash cardinality is between 1.0 and 2.0 of key cardinality - int minKeyBits = 64 - Long.numberOfLeadingZeros(entriesPerSegment - 1L); - // This minimizes probability of hash collision => on search, additional cache line touch - // and key comparison, that is quite high cost. - // between 8.0 and 16.0 - minKeyBits += 3; - int actualEntryBits = (int) BYTES.align((long) (minKeyBits + valueBits), BITS); - // devote the rest bits for key - return actualEntryBits - valueBits; - } - - public static int entrySize(int keyBits, int valueBits) { - return (int) BYTES.alignAndConvert((long) (keyBits + valueBits), BITS); - } - - public static long capacityFor(long entriesPerSegment) { - if (entriesPerSegment < 0L) - throw new IllegalArgumentException("entriesPerSegment should be positive"); - long capacity = Maths.nextPower2(entriesPerSegment, 64L); - if (((double) entriesPerSegment) / (double) capacity > 2./3.) { - // hash lookup shouldn't be too dense - capacity <<= 1L; - } - return capacity; - } - - private static final long UNSET_KEY = 0L; - private static final long UNSET_ENTRY = 0L; - - private static long mask(int bits) { - return (1L << bits) - 1L; - } - - private long address; - - private long capacityMask; - - private int entrySize; - private long capacityMask2; - - private int keyBits; - private long keyMask; - private long valueMask; - private long entryMask; - - // search state - private long searchKey = UNSET_KEY; - private long searchStartPos = -1L; - private long searchPos = -1L; - - public void reuse(long address, long capacity, int entrySize, int keyBits, int valueBits) { - this.address = address; - - this.capacityMask = capacity - 1L; - - this.entrySize = entrySize; - this.capacityMask2 = capacityMask * entrySize; - - this.keyBits = keyBits; - this.keyMask = mask(keyBits); - this.valueMask = mask(valueBits); - this.entryMask = mask(keyBits + valueBits); - } - - private long indexToPos(long index) { - return index * entrySize; - } - - private long maskUnsetKey(long key) { - return (key &= keyMask) != UNSET_KEY ? key : keyMask; - } - - private void checkValueForPut(long value) { - assert (value & ~valueMask) == 0L : "Value out of range, was " + value; - } - - private boolean empty(long entry) { - return (entry & entryMask) == UNSET_ENTRY; - } - - private long key(long entry) { - return entry & keyMask; - } - - private long value(long entry) { - return (entry >>> keyBits) & valueMask; - } - - private long entry(long key, long value) { - return key | (value << keyBits); - } - - private long pos(long key) { - return indexToPos(key & capacityMask); - } - - private long step(long pos) { - return (pos += entrySize) <= capacityMask2 ? pos : 0L; - } - - private long stepBack(long pos) { - return (pos -= entrySize) >= 0 ? pos : capacityMask2; - } - - private long readEntry(long pos) { - return UNSAFE.getLong(address + pos); - } - - private void writeEntry(long pos, long prevEntry, long key, long value) { - long entry = (prevEntry & ~entryMask) | entry(key, value); - UNSAFE.putLong(address + pos, entry); - } - - private void writeEntryVolatile(long pos, long prevEntry, long key, long value) { - long entry = (prevEntry & ~entryMask) | entry(key, value); - UNSAFE.putLongVolatile(null, address + pos, entry); - } - - private void writeEntry(long pos, long prevEntry, long anotherEntry) { - long entry = (prevEntry & ~entryMask) | (anotherEntry & entryMask); - UNSAFE.putLong(address + pos, entry); - } - - private void clearEntry(long pos, long prevEntry) { - long entry = (prevEntry & ~entryMask); - UNSAFE.putLong(address + pos, entry); - } - - public void init0(long key) { - key = maskUnsetKey(key); - searchKey = key; - searchStartPos = pos(key); - } - - public boolean isInit() { - return searchKey != UNSET_KEY; - } - - public void close0() { - searchKey = UNSET_KEY; - } - - public void initSearch0() { - searchPos = searchStartPos; - } - - public boolean isSearchInit() { - return searchPos >= 0L; - } - - public void closeSearch0() { - searchPos = -1L; - } - - public long nextPos() { - long pos = searchPos; - while (true) { - long entry = readEntry(pos); - if (empty(entry)) { - searchPos = pos; - return -1L; - } - pos = step(pos); - if (pos == searchStartPos) - break; - if (key(entry) == searchKey) { - searchPos = pos; - return value(entry); - } - } - throw new IllegalStateException("MultiMap is full, that most likely means you " + - "misconfigured entrySize/chunkSize, and entries tend to take less chunks than " + - "expected"); - } - - public void found() { - searchPos = stepBack(searchPos); - } - - public void remove() { - // for support of patterns like context.remove().put() - searchPos = remove0(); - } - - private long remove0() { - return remove00(searchPos); - } - - private long remove00(long posToRemove) { - long entryToRemove = readEntry(posToRemove); - long posToShift = posToRemove; - while (true) { - posToShift = step(posToShift); - long entryToShift = readEntry(posToShift); - if (empty(entryToShift)) - break; - long insertPos = pos(key(entryToShift)); - // the following condition essentially means circular permutations - // of three (r = posToRemove, s = posToShift, i = insertPos) - // positions are accepted: - // [...i..r...s.] or - // [...r..s...i.] or - // [...s..i...r.] - boolean cond1 = insertPos <= posToRemove; - boolean cond2 = posToRemove <= posToShift; - if ((cond1 && cond2) || - // chain wrapped around capacity - (posToShift < insertPos && (cond1 || cond2))) { - writeEntry(posToRemove, entryToRemove, entryToShift); - posToRemove = posToShift; - entryToRemove = entryToShift; - } - } - clearEntry(posToRemove, entryToRemove); - return posToRemove; - } - - public void put(long value) { - checkValueForPut(value); - writeEntry(searchPos, readEntry(searchPos), searchKey, value); - } - - public void putVolatile(long value) { - checkValueForPut(value); - writeEntryVolatile(searchPos, readEntry(searchPos), searchKey, value); - } - - public void clear() { - UNSAFE.setMemory(address, capacityMask2 + entrySize, (byte) 0); - } - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("{"); - forEach((key, value) -> sb.append(key).append('=').append(value).append(',')); - sb.append('}'); - return sb.toString(); - } - - public void forEach(EntryConsumer action) { - for (long pos = 0L; pos <= capacityMask2; pos += entrySize) { - long entry = readEntry(pos); - if (!empty(entry)) - action.accept(key(entry), value(entry)); - } - } - - public void forEachRemoving(HashLookupIteration iteration) { - long pos = 0L; - while (!empty(readEntry(pos))) { - pos = step(pos); - } - long startPos = pos; - do { - pos = step(pos); - long entry = readEntry(pos); - if (!empty(entry)) { - iteration.accept(key(entry), value(entry)); - if (iteration.remove()) { - remove00(pos); - pos = stepBack(pos); - } - } - } while (pos != startPos && iteration.continueIteration()); - } -} diff --git a/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/HashLookupIteration.java b/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/HashLookupIteration.java deleted file mode 100644 index 6579fa49e..000000000 --- a/src/main/java/net/openhft/chronicle/hash/impl/hashlookup/HashLookupIteration.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2015 higherfrequencytrading.com - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License. - * - * 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package net.openhft.chronicle.hash.impl.hashlookup; - -public interface HashLookupIteration { - void accept(long hash, long pos); - - boolean remove(); - - boolean continueIteration(); -} diff --git a/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/HashLookup.java b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/HashLookup.java index 4d0357fa9..1445af585 100644 --- a/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/HashLookup.java +++ b/src/main/java/net/openhft/chronicle/hash/impl/stage/entry/HashLookup.java @@ -17,7 +17,6 @@ package net.openhft.chronicle.hash.impl.stage.entry; import net.openhft.chronicle.hash.impl.VanillaChronicleHashHolder; -import net.openhft.chronicle.hash.impl.hashlookup.EntryConsumer; import net.openhft.lang.Maths; import net.openhft.sg.Stage; import net.openhft.sg.StageRef; @@ -219,6 +218,10 @@ public class HashLookup { return posToRemove; } + interface EntryConsumer { + void accept(long key, long value); + } + String hashLookupToString() { final StringBuilder sb = new StringBuilder("{"); forEach((key, value) -> sb.append(key).append('=').append(value).append(',')); diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index c58eed9c5..bc53c9d7f 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -19,12 +19,12 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.ChronicleHashBuilder; import net.openhft.chronicle.hash.ChronicleHashInstanceBuilder; import net.openhft.chronicle.hash.impl.ChronicleHashBuilderImpl; -import net.openhft.chronicle.hash.impl.hashlookup.HashLookup; -import net.openhft.chronicle.hash.serialization.internal.SerializationBuilder; +import net.openhft.chronicle.hash.impl.stage.entry.HashLookup; import net.openhft.chronicle.hash.replication.*; import net.openhft.chronicle.hash.serialization.*; import net.openhft.chronicle.hash.serialization.internal.MetaBytesWriter; import net.openhft.chronicle.hash.serialization.internal.MetaProvider; +import net.openhft.chronicle.hash.serialization.internal.SerializationBuilder; import net.openhft.chronicle.map.replication.MapRemoteOperations; import net.openhft.chronicle.set.ChronicleSetBuilder; import net.openhft.lang.Maths; @@ -56,8 +56,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import static java.lang.Math.round; import static net.openhft.chronicle.hash.impl.util.Objects.builderEquals; -import static net.openhft.chronicle.map.DefaultSpi.*; import static net.openhft.chronicle.map.DefaultSpi.mapEntryOperations; +import static net.openhft.chronicle.map.DefaultSpi.mapRemoteOperations; import static net.openhft.lang.model.DataValueGenerator.firstPrimitiveFieldType; /** diff --git a/src/test/java/net/openhft/chronicle/map/HashLookupTest.java b/src/test/java/net/openhft/chronicle/map/HashLookupTest.java deleted file mode 100755 index 79c28abb4..000000000 --- a/src/test/java/net/openhft/chronicle/map/HashLookupTest.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2015 higherfrequencytrading.com - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License. - * - * 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 Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package net.openhft.chronicle.map; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import net.openhft.chronicle.hash.impl.hashlookup.EntryConsumer; -import net.openhft.chronicle.hash.impl.hashlookup.HashLookup; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; - -import java.util.*; - -import static net.openhft.lang.io.NativeBytes.UNSAFE; - -/** - * User: peter.lawrey Date: 09/12/13 - */ -@RunWith(value = Parameterized.class) -public class HashLookupTest { - - static final long CAPACITY = 64; - static long address; - HashLookup map = new HashLookup(); - Multimap referenceMap = HashMultimap.create(); - - public HashLookupTest(Integer entrySize, Integer keyBits, Integer valueBits) - throws Exception { - map.reuse(address, CAPACITY, entrySize, keyBits, valueBits); - clean(); - } - - void clean() { - UNSAFE.setMemory(address, CAPACITY * 8L, (byte) 0); - } - - @BeforeClass - public static void setUp() { - address = UNSAFE.allocateMemory(CAPACITY * 8L); - } - - @AfterClass - public static void tearDown() { - UNSAFE.freeMemory(address); - } - - @Parameterized.Parameters - public static Collection data() { - return Arrays.asList(new Object[][]{ - {3, 12, 9}, - {4, 18, 14}, - {8, 33, 29}, - }); - } - - private void multiMapEquals() { - class Action implements EntryConsumer { - int mapSize = 0; - - @Override - public void accept(long key, long value) { - mapSize++; - Assert.assertTrue(referenceMap.containsEntry(key, value)); - } - } - Action action = new Action(); - map.forEach(action); - Assert.assertEquals(referenceMap.size(), action.mapSize); - } - - private void valuesEqualsByKey(long k) { - List values = new ArrayList<>(); - map.init0(k); - map.initSearch0(); - long v; - while ((v = map.nextPos()) >= 0L) - values.add(v); - Set valueSet = new HashSet(values); - Assert.assertEquals(values.size(), valueSet.size()); - Assert.assertEquals(new HashSet<>(referenceMap.get(k)), valueSet); - } - - private void put(long k, long v) { - put0(k, v); - referenceMap.put(k, v); - } - - private void put0(long k, long v) { - map.init0(k); - map.initSearch0(); - while (map.nextPos() >= 0L) - ; - map.put(v); - map.closeSearch0(); - map.close0(); - } - - private void remove(long k, long value) { - long v = remove0(k, value); - referenceMap.remove(k, v); - } - - private long remove0(long k, long value) { - map.init0(k); - map.initSearch0(); - long v; - while ((v = map.nextPos()) >= 0L && v != value) - ; - map.found(); - map.remove(); - map.closeSearch0(); - map.close0(); - return v; - } - - @Test - public void testPutRemoveSearch() { - clean(); - multiMapEquals(); - put(1, 11); - valuesEqualsByKey(1); - - multiMapEquals(); - put(3, 33); - multiMapEquals(); - put(1, 12); - put(1, 13); - put(1, 14); - put(3, 32); - put(1, 15); - multiMapEquals(); - - remove(1, 11); - multiMapEquals(); - - valuesEqualsByKey(3); - valuesEqualsByKey(1); - - remove(1, 12); - multiMapEquals(); - - remove(1, 15); - multiMapEquals(); - - remove(1, 13); - multiMapEquals(); - - remove(1, 14); - multiMapEquals(); - } - - @Test - public void testRemoveSpecific() { - // Testing a specific case when the remove method on the map - // does (did) not work as expected. The size goes correctly to - // 0 but the value is still present in the map. - clean(); - - put0(15, 1); - remove0(15, 1); - map.init0(15); - map.initSearch0(); - Assert.assertTrue(map.nextPos() < 0); - } -}