HCOLL-360 Add segment tiers

This commit is contained in:
leventov
2015-09-17 20:58:52 +03:00
parent 4606b64a02
commit 2eed65e80e
39 changed files with 1182 additions and 285 deletions
@@ -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));
}
}
}