mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
HCOLL-360 Add segment tiers
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package net.openhft.chronicle.hash.impl;
|
||||
|
||||
import net.openhft.chronicle.bytes.NativeBytesStore;
|
||||
import net.openhft.chronicle.core.Maths;
|
||||
import net.openhft.chronicle.hash.*;
|
||||
import net.openhft.chronicle.hash.impl.util.BuildVersion;
|
||||
import net.openhft.chronicle.hash.serialization.BytesReader;
|
||||
@@ -24,10 +26,7 @@ import net.openhft.chronicle.hash.serialization.internal.MetaBytesInterop;
|
||||
import net.openhft.chronicle.hash.serialization.internal.MetaProvider;
|
||||
import net.openhft.chronicle.hash.serialization.internal.SerializationBuilder;
|
||||
import net.openhft.chronicle.map.ChronicleMapBuilder;
|
||||
import net.openhft.lang.io.Bytes;
|
||||
import net.openhft.lang.io.BytesStore;
|
||||
import net.openhft.lang.io.MappedStore;
|
||||
import net.openhft.lang.io.NativeBytes;
|
||||
import net.openhft.lang.io.*;
|
||||
import net.openhft.lang.io.serialization.BytesMarshallableSerializer;
|
||||
import net.openhft.lang.threadlocal.Provider;
|
||||
|
||||
@@ -36,14 +35,14 @@ import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.Serializable;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.Long.numberOfTrailingZeros;
|
||||
import static java.lang.Math.max;
|
||||
import static net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable.entrySize;
|
||||
import static net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable.keyBits;
|
||||
import static net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable.valueBits;
|
||||
import static net.openhft.chronicle.hash.impl.CompactOffHeapLinearHashTable.*;
|
||||
import static net.openhft.lang.MemoryUnit.*;
|
||||
import static net.openhft.lang.io.NativeBytes.UNSAFE;
|
||||
|
||||
public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K, ? super KI>,
|
||||
C extends HashEntry<K>, SC extends HashSegmentContext<K, ?>,
|
||||
@@ -101,13 +100,44 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
public final int segmentEntrySpaceInnerOffset;
|
||||
final long segmentEntrySpaceOuterSize;
|
||||
|
||||
final long segmentSize;
|
||||
public final long segmentSize;
|
||||
|
||||
final long maxExtraTiers;
|
||||
final long tierBulkBytesSize;
|
||||
final long tierBulkInnerOffsetToTiers;
|
||||
protected final long numberOfTiersInBulk;
|
||||
protected final int log2NumberOfTiersInBulk;
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Bytes Store (essentially, the base address) and serialization-dependent offsets
|
||||
public transient BytesStore ms;
|
||||
protected transient BytesStore ms;
|
||||
transient Bytes bytes;
|
||||
|
||||
public static class TierBulkData {
|
||||
public BytesStore langBytesStore;
|
||||
public Bytes langBytes;
|
||||
public net.openhft.chronicle.bytes.BytesStore chBytesBytesStore;
|
||||
|
||||
public long offset;
|
||||
|
||||
public TierBulkData(BytesStore langBytesStore, long offset) {
|
||||
this.langBytesStore = langBytesStore;
|
||||
langBytes = langBytesStore.bytes();
|
||||
chBytesBytesStore = new NativeBytesStore<>(
|
||||
langBytes.address(), langBytes.capacity(), null, false);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public TierBulkData(TierBulkData data, long offset) {
|
||||
this.langBytesStore = data.langBytesStore;
|
||||
this.langBytes = data.langBytes;
|
||||
this.chBytesBytesStore = data.chBytesBytesStore;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
public transient List<TierBulkData> tierBulkOffsets;
|
||||
|
||||
public transient long headerSize;
|
||||
transient long segmentHeadersOffset;
|
||||
transient long segmentsOffset;
|
||||
@@ -167,12 +197,17 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
segmentEntrySpaceInnerOffset + segmentEntrySpaceInnerSize, BYTES);
|
||||
|
||||
segmentSize = segmentSize();
|
||||
|
||||
maxExtraTiers = privateAPI.maxExtraTiers(replicated);
|
||||
numberOfTiersInBulk = computeNumberOfTiersInBulk();
|
||||
log2NumberOfTiersInBulk = Maths.intLog2(numberOfTiersInBulk);
|
||||
tierBulkInnerOffsetToTiers = computeTierBulkInnerOffsetToTiers(numberOfTiersInBulk);
|
||||
tierBulkBytesSize = computeTierBulkBytesSize();
|
||||
}
|
||||
|
||||
private long segmentSize() {
|
||||
long ss = segmentHashLookupOuterSize
|
||||
+ segmentFreeListOuterSize
|
||||
+ segmentEntrySpaceOuterSize;
|
||||
long ss = segmentHashLookupOuterSize + 64 /* Tier cache line */ +
|
||||
segmentFreeListOuterSize + segmentEntrySpaceOuterSize;
|
||||
if ((ss & 63L) != 0)
|
||||
throw new AssertionError();
|
||||
return breakL1CacheAssociativityContention(ss);
|
||||
@@ -190,6 +225,25 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
return segmentSize;
|
||||
}
|
||||
|
||||
private long computeNumberOfTiersInBulk() {
|
||||
// TODO review heuristics
|
||||
int tiersInBulk = actualSegments / 8;
|
||||
tiersInBulk = Maths.nextPower2(tiersInBulk, 1);
|
||||
while (segmentSize * tiersInBulk < UNSAFE.pageSize()) {
|
||||
tiersInBulk *= 2;
|
||||
}
|
||||
return tiersInBulk;
|
||||
}
|
||||
|
||||
private long computeTierBulkBytesSize() {
|
||||
return roundUpRuntimePageSize(tierBulkInnerOffsetToTiers +
|
||||
numberOfTiersInBulk * segmentSize);
|
||||
}
|
||||
|
||||
protected long computeTierBulkInnerOffsetToTiers(long numberOfTiersInBulk) {
|
||||
return 0L;
|
||||
}
|
||||
|
||||
public void initTransients() {
|
||||
ownInitTransients();
|
||||
}
|
||||
@@ -209,16 +263,39 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
segmentHeadersOffset = mapHeaderOuterSize();
|
||||
long segmentHeadersSize = actualSegments * segmentHeaderSize;
|
||||
segmentsOffset = segmentHeadersOffset + segmentHeadersSize;
|
||||
|
||||
initTierBulks(bytesStore.size());
|
||||
}
|
||||
|
||||
public final void createMappedStoreAndSegments(File file) throws IOException {
|
||||
// TODO this method had been moved -- not clear where
|
||||
//OS.warnOnWindows(sizeInBytes());
|
||||
//OS.warnOnWindows(sizeInBytesWithoutTiers());
|
||||
createMappedStoreAndSegments(new MappedStore(file, FileChannel.MapMode.READ_WRITE,
|
||||
sizeInBytes(), BytesMarshallableSerializer.create()));
|
||||
// file.length() > sizeInBytesWithoutTiers() means there are some tiered segments
|
||||
Math.max(sizeInBytesWithoutTiers(), file.length()),
|
||||
BytesMarshallableSerializer.create()));
|
||||
// newly-extended file contents are not guaranteed to be zero
|
||||
if (created)
|
||||
if (created) {
|
||||
// TODO zero out only hash lookup tables and bit sets, not entry data areas
|
||||
bytes.zeroOut(headerSize, bytes.capacity(), true);
|
||||
// zero out tier data
|
||||
bytes.zeroOut(headerSize, headerSize + 64, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void initTierBulks(long byteStoreSize) {
|
||||
tierBulkOffsets = new ArrayList<>();
|
||||
long tierBulkOffset = roundUpRuntimePageSize(sizeInBytesWithoutTiers());
|
||||
if (byteStoreSize >= tierBulkOffset + tierBulkBytesSize) {
|
||||
TierBulkData firstTierBulkData = new TierBulkData(ms, tierBulkOffset);
|
||||
tierBulkOffsets.add(firstTierBulkData);
|
||||
while (true) {
|
||||
tierBulkOffset += tierBulkBytesSize;
|
||||
if (byteStoreSize < tierBulkOffset + tierBulkBytesSize)
|
||||
break;
|
||||
tierBulkOffsets.add(new TierBulkData(firstTierBulkData, tierBulkOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
@@ -246,12 +323,16 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
private long mapHeaderOuterSize() {
|
||||
// Align segment headers on page boundary to minimize number of pages that
|
||||
// segment headers span
|
||||
long pageMask = NativeBytes.UNSAFE.pageSize() - 1L;
|
||||
return (mapHeaderInnerSize() + pageMask) & ~pageMask;
|
||||
return roundUpRuntimePageSize(mapHeaderInnerSize());
|
||||
}
|
||||
|
||||
private static long roundUpRuntimePageSize(long size) {
|
||||
long pageMask = UNSAFE.pageSize() - 1;
|
||||
return (size + pageMask) & ~pageMask;
|
||||
}
|
||||
|
||||
public long mapHeaderInnerSize() {
|
||||
return headerSize;
|
||||
return headerSize + 64; /* tier data cache line */
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -259,7 +340,7 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
return ms.file();
|
||||
}
|
||||
|
||||
public final long sizeInBytes() {
|
||||
public final long sizeInBytesWithoutTiers() {
|
||||
return mapHeaderOuterSize() + actualSegments * (segmentHeaderSize + segmentSize);
|
||||
}
|
||||
|
||||
@@ -295,17 +376,21 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
public final long[] segmentSizes() {
|
||||
long[] sizes = new long[actualSegments];
|
||||
for (int i = 0; i < actualSegments; i++) {
|
||||
sizes[i] = BigSegmentHeader.INSTANCE.size(ms.address() + segmentHeaderOffset(i));
|
||||
sizes[i] = BigSegmentHeader.INSTANCE.size(segmentHeaderAddress(i));
|
||||
}
|
||||
return sizes;
|
||||
}
|
||||
|
||||
public final long segmentHeaderOffset(int segmentIndex) {
|
||||
return segmentHeadersOffset + ((long) segmentIndex) * segmentHeaderSize;
|
||||
public final long segmentHeaderAddress(int segmentIndex) {
|
||||
return ms.address() + segmentHeadersOffset + ((long) segmentIndex) * segmentHeaderSize;
|
||||
}
|
||||
|
||||
public final long segmentOffset(int segmentIndex) {
|
||||
return segmentsOffset + ((long) segmentIndex) * segmentSize;
|
||||
public final long segmentBaseAddr(int segmentIndex) {
|
||||
return ms.address() + msBytesOffset(segmentIndex);
|
||||
}
|
||||
|
||||
private long msBytesOffset(long segmentIndex) {
|
||||
return segmentsOffset + segmentIndex * segmentSize;
|
||||
}
|
||||
|
||||
public final int inChunks(long sizeInBytes) {
|
||||
@@ -323,7 +408,7 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
public final long longSize() {
|
||||
long result = 0L;
|
||||
for (int i = 0; i < actualSegments; i++) {
|
||||
long segmentHeaderAddress = ms.address() + segmentHeaderOffset(i);
|
||||
long segmentHeaderAddress = segmentHeaderAddress(i);
|
||||
result += BigSegmentHeader.INSTANCE.size(segmentHeaderAddress) -
|
||||
BigSegmentHeader.INSTANCE.deleted(segmentHeaderAddress);
|
||||
}
|
||||
@@ -339,4 +424,188 @@ public abstract class VanillaChronicleHash<K, KI, MKI extends MetaBytesInterop<K
|
||||
public int segments() {
|
||||
return actualSegments;
|
||||
}
|
||||
|
||||
private static final long TIER_DATA_LOCK_OFFSET = 0L;
|
||||
private static final long EXTRA_ALLOCATED_TIER_BULK_COUNT_OFFSET = TIER_DATA_LOCK_OFFSET + 8L;
|
||||
private static final long FIRST_FREE_TIER_INDEX_OFFSET =
|
||||
EXTRA_ALLOCATED_TIER_BULK_COUNT_OFFSET + 8L;
|
||||
private static final long TIERS_IN_USE_OFFSET = FIRST_FREE_TIER_INDEX_OFFSET + 8L;
|
||||
|
||||
private long tierDataAddress() {
|
||||
return bytes.address() + headerSize;
|
||||
}
|
||||
|
||||
private void tierDataLock() {
|
||||
// TODO this is quick access to locking functionality, eligible only because lock word
|
||||
// in the segment header has offset = 0
|
||||
BigSegmentHeader.INSTANCE.writeLock(tierDataAddress() + TIER_DATA_LOCK_OFFSET);
|
||||
}
|
||||
|
||||
private void tierDataUnlock() {
|
||||
BigSegmentHeader.INSTANCE.writeUnlock(tierDataAddress() + TIER_DATA_LOCK_OFFSET);
|
||||
}
|
||||
|
||||
protected long extraAllocatedTierBulkCount() {
|
||||
return bytes.readLong(headerSize + EXTRA_ALLOCATED_TIER_BULK_COUNT_OFFSET);
|
||||
}
|
||||
|
||||
private void extraAllocatedTierBulkCount(long extraAllocatedTierBulkCount) {
|
||||
bytes.writeLong(headerSize + EXTRA_ALLOCATED_TIER_BULK_COUNT_OFFSET,
|
||||
extraAllocatedTierBulkCount);
|
||||
}
|
||||
|
||||
private long firstFreeTierIndex() {
|
||||
return bytes.readLong(headerSize + FIRST_FREE_TIER_INDEX_OFFSET);
|
||||
}
|
||||
|
||||
private void firstFreeTierIndex(long firstFreeTierIndex) {
|
||||
bytes.writeLong(headerSize + FIRST_FREE_TIER_INDEX_OFFSET, firstFreeTierIndex);
|
||||
}
|
||||
|
||||
private long tiersInUse() {
|
||||
return bytes.readLong(headerSize + TIERS_IN_USE_OFFSET);
|
||||
}
|
||||
|
||||
private void tiersInUse(long tiersInUse) {
|
||||
bytes.writeLong(headerSize + TIERS_IN_USE_OFFSET, tiersInUse);
|
||||
}
|
||||
|
||||
public long allocateTier(int forSegmentIndex, int tier) {
|
||||
tierDataLock();
|
||||
try {
|
||||
long tiersInUse = tiersInUse();
|
||||
if (tiersInUse == maxExtraTiers) {
|
||||
throw new IllegalStateException("Attempt to allocate " + (maxExtraTiers + 1) +
|
||||
"th extra segment tier, " + maxExtraTiers + " is maximum.\n" +
|
||||
"Possible reasons include:\n" +
|
||||
" - you have forgotten to configure (or configured wrong) " +
|
||||
"builder.entries() number\n" +
|
||||
" - same regarding other sizing Chronicle Hash configurations, most " +
|
||||
"likely maxBloatFactor(), averageKeySize(), or averageValueSize()\n" +
|
||||
" - keys, inserted into the ChronicleHash, are distributed suspiciously " +
|
||||
"bad. This might be a DOS attack");
|
||||
}
|
||||
while (true) {
|
||||
long firstFreeTierIndex = firstFreeTierIndex();
|
||||
if (firstFreeTierIndex > 0) {
|
||||
tiersInUse(tiersInUse + 1);
|
||||
long tierBaseAddr = tierIndexToBaseAddr(firstFreeTierIndex);
|
||||
long tierDataAddr = tierBaseAddr + segmentHashLookupOuterSize;
|
||||
long nextFreeTierIndex = TierData.nextTierIndex(tierDataAddr);
|
||||
// now, when this tier will be a part of the map, the next tier designates
|
||||
// the next tier in the data structure, should be 0
|
||||
TierData.nextTierIndex(tierDataAddr, 0);
|
||||
TierData.segmentIndex(tierDataAddr, forSegmentIndex);
|
||||
TierData.tier(tierDataAddr, tier);
|
||||
firstFreeTierIndex(nextFreeTierIndex);
|
||||
return firstFreeTierIndex;
|
||||
} else {
|
||||
allocateTierBulk();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
tierDataUnlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void allocateTierBulk() {
|
||||
long alreadyAllocatedBulks = extraAllocatedTierBulkCount();
|
||||
mapTiers(alreadyAllocatedBulks);
|
||||
extraAllocatedTierBulkCount(alreadyAllocatedBulks + 1);
|
||||
linkFreeTiersList(alreadyAllocatedBulks);
|
||||
}
|
||||
|
||||
private void linkFreeTiersList(long alreadyAllocatedBulks) {
|
||||
long alreadyAllocatedTiers = alreadyAllocatedBulks * numberOfTiersInBulk;
|
||||
for (long tierIndex = alreadyAllocatedTiers;
|
||||
tierIndex < alreadyAllocatedTiers + numberOfTiersInBulk - 1; tierIndex++) {
|
||||
long tierBaseAddr = tierIndexToBaseAddr(tierIndex);
|
||||
long tierDataAddr = tierBaseAddr + segmentHashLookupOuterSize;
|
||||
TierData.nextTierIndex(tierDataAddr, tierIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public long tierIndexToBaseAddr(long tierIndex) {
|
||||
// tiers are 1-counted, to allow tierIndex = 0 to be un-initialized in off-heap memory,
|
||||
// convert into 0-based form
|
||||
tierIndex -= 1;
|
||||
if (tierIndex < actualSegments)
|
||||
return segmentBaseAddr((int) tierIndex);
|
||||
return extraTierIndexToBaseAddress(tierIndex);
|
||||
}
|
||||
|
||||
public Bytes tierBytes(long tierIndex) {
|
||||
tierIndex -= 1;
|
||||
if (tierIndex < actualSegments)
|
||||
return bytes;
|
||||
return tierBulkData(tierIndex).langBytes;
|
||||
}
|
||||
|
||||
public long bytesOffset(long tierIndex) {
|
||||
tierIndex -= 1;
|
||||
if (tierIndex < actualSegments)
|
||||
return msBytesOffset(tierIndex);
|
||||
return tierBulkData(tierIndex).offset;
|
||||
}
|
||||
|
||||
private TierBulkData tierBulkData(long tierIndex) {
|
||||
long extraTierIndex = tierIndex - actualSegments;
|
||||
long bulkIndex = extraTierIndex >> log2NumberOfTiersInBulk;
|
||||
if (bulkIndex >= tierBulkOffsets.size())
|
||||
mapTiers(bulkIndex);
|
||||
return tierBulkOffsets.get((int) bulkIndex);
|
||||
}
|
||||
|
||||
private long extraTierIndexToBaseAddress(long tierIndex) {
|
||||
long extraTierIndex = tierIndex - actualSegments;
|
||||
long bulkIndex = extraTierIndex >> log2NumberOfTiersInBulk;
|
||||
if (bulkIndex >= tierBulkOffsets.size())
|
||||
mapTiers(bulkIndex);
|
||||
TierBulkData tierBulkData = tierBulkOffsets.get((int) bulkIndex);
|
||||
return tierBulkData.langBytes.address() + tierBulkData.offset +
|
||||
tierBulkInnerOffsetToTiers +
|
||||
(extraTierIndex & (numberOfTiersInBulk - 1)) * segmentSize;
|
||||
}
|
||||
|
||||
private void mapTiers(long upToBulkIndex) {
|
||||
if (ms instanceof MappedStore) {
|
||||
try {
|
||||
mapTiersMapped(upToBulkIndex);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
// in-memory ChMap
|
||||
allocateTiers(upToBulkIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void mapTiersMapped(long upToBulkIndex) throws IOException {
|
||||
int firstBulkToMap = tierBulkOffsets.size();
|
||||
long bulksToMap = upToBulkIndex + 1 - firstBulkToMap;
|
||||
long mapSize = bulksToMap * tierBulkBytesSize;
|
||||
long mapStart = roundUpRuntimePageSize(sizeInBytesWithoutTiers()) +
|
||||
firstBulkToMap * tierBulkBytesSize;
|
||||
MappedStore extraStore = new MappedStore(file(), FileChannel.MapMode.READ_WRITE,
|
||||
mapStart, mapSize, ms.objectSerializer());
|
||||
appendTierBulkData(upToBulkIndex, firstBulkToMap, extraStore);
|
||||
}
|
||||
|
||||
private void allocateTiers(long upToBulkIndex) {
|
||||
int firstBulkToMap = tierBulkOffsets.size();
|
||||
long bulksToMap = upToBulkIndex + 1 - firstBulkToMap;
|
||||
long mapSize = bulksToMap * tierBulkBytesSize;
|
||||
DirectStore extraStore = new DirectStore(ms.objectSerializer(), mapSize, true);
|
||||
appendTierBulkData(upToBulkIndex, firstBulkToMap, extraStore);
|
||||
}
|
||||
|
||||
private void appendTierBulkData(long upToBulkIndex, int firstBulkToMap, BytesStore extraStore) {
|
||||
long offset = 0;
|
||||
TierBulkData firstMappedTierBulkData = new TierBulkData(extraStore, offset);
|
||||
tierBulkOffsets.add(firstMappedTierBulkData);
|
||||
for (long i = firstBulkToMap + 1; i <= upToBulkIndex; i++) {
|
||||
tierBulkOffsets.add(new TierBulkData(firstMappedTierBulkData,
|
||||
offset += tierBulkBytesSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user