mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
Consolidate segment headers to reduce TLB misses. Re-tune the calculated number of segments based on the size of data.
This commit is contained in:
@@ -22,8 +22,6 @@ import net.openhft.chronicle.hash.*;
|
||||
import net.openhft.chronicle.hash.serialization.MetaBytesInterop;
|
||||
import net.openhft.chronicle.hash.serialization.MetaBytesWriter;
|
||||
import net.openhft.chronicle.hash.serialization.MetaProvider;
|
||||
import net.openhft.lang.threadlocal.Provider;
|
||||
import net.openhft.lang.threadlocal.ThreadLocalCopies;
|
||||
import net.openhft.chronicle.set.ChronicleSetBuilder;
|
||||
import net.openhft.lang.Maths;
|
||||
import net.openhft.lang.io.ByteBufferBytes;
|
||||
@@ -35,6 +33,8 @@ import net.openhft.lang.io.serialization.impl.AllocateInstanceObjectFactory;
|
||||
import net.openhft.lang.io.serialization.impl.NewInstanceObjectFactory;
|
||||
import net.openhft.lang.io.serialization.impl.VanillaBytesMarshallerFactory;
|
||||
import net.openhft.lang.model.Byteable;
|
||||
import net.openhft.lang.threadlocal.Provider;
|
||||
import net.openhft.lang.threadlocal.ThreadLocalCopies;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -48,18 +48,18 @@ import java.util.concurrent.TimeUnit;
|
||||
import static net.openhft.chronicle.map.Objects.builderEquals;
|
||||
|
||||
/**
|
||||
* {@code AbstractChronicleMapBuilder} manages most of {@link ChronicleMap} configurations; has two
|
||||
* concrete subclasses: {@link ChronicleMapBuilder} should be used to create maps with ordinary
|
||||
* values, {@link OffHeapUpdatableChronicleMapBuilder} -- maps with {@link Byteable} values, which
|
||||
* point directly to off-heap memory; could be used as a classic builder and/or factory. This means
|
||||
* {@code AbstractChronicleMapBuilder} manages most of {@link ChronicleMap} configurations; has two concrete subclasses:
|
||||
* {@link ChronicleMapBuilder} should be used to create maps with ordinary values, {@link
|
||||
* OffHeapUpdatableChronicleMapBuilder} -- maps with {@link Byteable} values, which point directly to off-heap memory;
|
||||
* could be used as a classic builder and/or factory. This means
|
||||
* that in addition to the standard builder usage pattern: <pre>{@code
|
||||
* ChronicleMap<Key, Value> map = ChronicleMapBuilder
|
||||
* .of(Key.class, Value.class)
|
||||
* .entries(100500)
|
||||
* // ... other configurations
|
||||
* .create();}</pre>
|
||||
* one of concrete {@code AbstractChronicleMapBuilder} subclasses, {@link ChronicleMapBuilder} or
|
||||
* {@link OffHeapUpdatableChronicleMapBuilder}, could be prepared and used to create many similar
|
||||
* one of concrete {@code AbstractChronicleMapBuilder} subclasses, {@link ChronicleMapBuilder} or {@link
|
||||
* OffHeapUpdatableChronicleMapBuilder}, could be prepared and used to create many similar
|
||||
* maps: <pre>{@code
|
||||
* OffHeapUpdatableChronicleMapBuilder<Key, Value> builder = OffHeapUpdatableChronicleMapBuilder
|
||||
* .of(Key.class, Value.class)
|
||||
@@ -69,22 +69,22 @@ import static net.openhft.chronicle.map.Objects.builderEquals;
|
||||
* ChronicleMap<Key, Value> map2 = builder.create();}</pre>
|
||||
* i. e. created {@code ChronicleMap} instances don't depend on the builder.
|
||||
*
|
||||
* <p>{@code AbstractChronicleMapBuilder} and it's subclasses are mutable, see a note in
|
||||
* {@link ChronicleHashBuilder} interface documentation.
|
||||
*
|
||||
* <p>Later in this documentation, "ChronicleMap" means "ChronicleMaps, created by {@code
|
||||
* AbstractChronicleMapBuilder}", unless specified different, because theoretically someone might
|
||||
* provide {@code ChronicleMap} implementations with completely different properties.
|
||||
*
|
||||
* <p>{@code ChronicleMap} ("ChronicleMaps, created by {@code AbstractChronicleMapBuilder}")
|
||||
* currently doesn't support resizing. That is why you should <i>always</i> configure {@linkplain
|
||||
* #entries(long) number of entries} you are going to insert into the created map <i>at most</i>.
|
||||
* See {@link #entries(long)} method documentation for more information on this.
|
||||
*
|
||||
* <p>{@code ChronicleMap} allocates memory by equally sized chunks. This size is called {@linkplain
|
||||
* #entrySize(int) entry size}, you are strongly recommended to configure it to achieve least memory
|
||||
* consumption and best speed. See {@link #entrySize(int)} method documentation for more information
|
||||
* <p>{@code AbstractChronicleMapBuilder} and it's subclasses are mutable, see a note in {@link ChronicleHashBuilder}
|
||||
* interface documentation.
|
||||
* </p>
|
||||
* <p>Later in this documentation, "ChronicleMap" means "ChronicleMaps, created by {@code AbstractChronicleMapBuilder}",
|
||||
* unless specified different, because theoretically someone might provide {@code ChronicleMap} implementations with
|
||||
* completely different properties.
|
||||
* </p>
|
||||
* <p>{@code ChronicleMap} ("ChronicleMaps, created by {@code AbstractChronicleMapBuilder}") currently doesn't support
|
||||
* resizing. That is why you should <i>always</i> configure {@linkplain #entries(long) number of entries} you are going
|
||||
* to insert into the created map <i>at most</i>. See {@link #entries(long)} method documentation for more information
|
||||
* on this.
|
||||
* </p>
|
||||
* <p>{@code ChronicleMap} allocates memory by equally sized chunks. This size is called {@linkplain #entrySize(int)
|
||||
* entry size}, you are strongly recommended to configure it to achieve least memory consumption and best speed. See
|
||||
* {@link #entrySize(int)} method documentation for more information on this.
|
||||
* </p>
|
||||
*
|
||||
* @param <K> key type of the maps, produced by this builder
|
||||
* @param <V> value type of the maps, produced by this builder
|
||||
@@ -95,13 +95,11 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
B extends AbstractChronicleMapBuilder<K, V, B>>
|
||||
implements Cloneable, ChronicleHashBuilder<K, ChronicleMap<K, V>, B> {
|
||||
|
||||
static final short UDP_REPLICATION_MODIFICATION_ITERATOR_ID = 128;
|
||||
private static final Bytes EMPTY_BYTES = new ByteBufferBytes(ByteBuffer.allocate(0));
|
||||
private static final int DEFAULT_KEY_OR_VALUE_SIZE = 120;
|
||||
|
||||
private static final int MAX_SEGMENTS = (1 << 30);
|
||||
private static final int MAX_SEGMENTS_TO_CHAISE_COMPACT_MULTI_MAPS = (1 << 20);
|
||||
|
||||
static final short UDP_REPLICATION_MODIFICATION_ITERATOR_ID = 128;
|
||||
private static final Logger LOG =
|
||||
LoggerFactory.getLogger(AbstractChronicleMapBuilder.class.getName());
|
||||
|
||||
@@ -157,6 +155,27 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
return roundUp;
|
||||
}
|
||||
|
||||
private static void checkSegments(int segments) {
|
||||
if (segments <= 0 || segments > MAX_SEGMENTS)
|
||||
throw new IllegalArgumentException("segments should be positive, " +
|
||||
segments + " given");
|
||||
if (segments > MAX_SEGMENTS)
|
||||
throw new IllegalArgumentException("Max segments is " + MAX_SEGMENTS + ", " +
|
||||
segments + " given");
|
||||
}
|
||||
|
||||
private static long divideUpper(long dividend, long divisor) {
|
||||
return ((dividend - 1L) / divisor) + 1L;
|
||||
}
|
||||
|
||||
private static String pretty(int value) {
|
||||
return value > 0 ? value + "" : "not configured";
|
||||
}
|
||||
|
||||
private static String pretty(Object obj) {
|
||||
return obj != null ? obj + "" : "not configured";
|
||||
}
|
||||
|
||||
@Override
|
||||
public B clone() {
|
||||
try {
|
||||
@@ -183,9 +202,8 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
* .keySize(10)
|
||||
* // shouldn't specify valueSize(), because it is statically known
|
||||
* .create();}</pre>
|
||||
* (Note that 10 is chosen as key size in bytes despite strings in Java are UTF-16 encoded (and
|
||||
* each character takes 2 bytes on-heap), because default off-heap {@link String} encoding is
|
||||
* UTF-8 in {@code ChronicleMap}.)
|
||||
* (Note that 10 is chosen as key size in bytes despite strings in Java are UTF-16 encoded (and each character takes
|
||||
* 2 bytes on-heap), because default off-heap {@link String} encoding is UTF-8 in {@code ChronicleMap}.)
|
||||
*
|
||||
* @see #constantKeySizeBySample(Object)
|
||||
* @see #entrySize(int)
|
||||
@@ -247,13 +265,11 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>In fully default case you can expect entry size to be about 256 bytes. But it is strongly
|
||||
* recommended always to configure {@linkplain #keySize(int) key size} and {@linkplain
|
||||
* #valueSize(int) value size}, if they couldn't be derived statically.
|
||||
*
|
||||
* <p>If entry size is not configured explicitly by calling this method, it is computed based on
|
||||
* {@linkplain #metaDataBytes(int) meta data bytes}, plus {@linkplain #keySize(int) key size},
|
||||
* plus {@linkplain #valueSize(int) value size}, plus a few bytes required by implementations.
|
||||
* <p>In fully default case you can expect entry size to be about 256 bytes. But it is strongly recommended always
|
||||
* to configure {@linkplain #keySize(int) key size} and {@linkplain #valueSize(int) value size}, if they couldn't be
|
||||
* derived statically. </p> <p>If entry size is not configured explicitly by calling this method, it is computed
|
||||
* based on {@linkplain #metaDataBytes(int) meta data bytes}, plus {@linkplain #keySize(int) key size}, plus
|
||||
* {@linkplain #valueSize(int) value size}, plus a few bytes required by implementations. </p>
|
||||
*
|
||||
* @see #entries(long)
|
||||
*/
|
||||
@@ -350,8 +366,8 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
double poorDistEntriesScale = Math.log(segments) * entries;
|
||||
if (segments <= 8)
|
||||
return Math.min(entries * segments,
|
||||
(long) (entries + poorDistEntriesScale * 0.08 + 64)); // 8% was min for tests
|
||||
return (long) (entries + poorDistEntriesScale * 0.11 + 80); // 11% was min for tests
|
||||
(long) (entries + poorDistEntriesScale * 0.11 + segments * 8)); // 11% was min for tests
|
||||
return (long) (entries + poorDistEntriesScale * 0.11 + 80); // 11% was min for tests
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -366,17 +382,20 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
}
|
||||
|
||||
private int estimateSegments() {
|
||||
for (int power = 0; power < 7; power++) {
|
||||
if (entries < (1L << (7 + (2 * power))))
|
||||
return 1 << power;
|
||||
for (int power = 1; power < 64; power *= 2) {
|
||||
if (entries <= power * power * power)
|
||||
return power;
|
||||
}
|
||||
// Heuristic -- number of segments ~= cube root from number of entries seems optimal
|
||||
int maxSegments = 65536;
|
||||
for (int segments = 4; segments < maxSegments; segments <<= 1) {
|
||||
if (((long) segments) * segments * segments >= entries)
|
||||
return segments;
|
||||
}
|
||||
return maxSegments;
|
||||
long size = entrySize * entries;
|
||||
if (size < 200L * 1000 * 1000) // 200 MB
|
||||
return 64;
|
||||
if (size < 2000L * 1000 * 1000) // 2 GB
|
||||
return 128;
|
||||
if (size < 8000L * 1000 * 1000) // 8 GB
|
||||
return 256;
|
||||
if (size < 20L * 1000 * 1000 * 1000) // 20 GB
|
||||
return 512;
|
||||
return 1024;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -386,15 +405,6 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
return self();
|
||||
}
|
||||
|
||||
private static void checkSegments(int segments) {
|
||||
if (segments <= 0 || segments > MAX_SEGMENTS)
|
||||
throw new IllegalArgumentException("segments should be positive, " +
|
||||
segments + " given");
|
||||
if (segments > MAX_SEGMENTS)
|
||||
throw new IllegalArgumentException("Max segments is " + MAX_SEGMENTS + ", " +
|
||||
segments + " given");
|
||||
}
|
||||
|
||||
int actualSegments() {
|
||||
if (actualSegments > 0)
|
||||
return actualSegments;
|
||||
@@ -417,10 +427,6 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
return segments <= maxSegments ? segments : -segments;
|
||||
}
|
||||
|
||||
private static long divideUpper(long dividend, long divisor) {
|
||||
return ((dividend - 1L) / divisor) + 1L;
|
||||
}
|
||||
|
||||
private boolean canSupportShortShort() {
|
||||
return entries > (long) minSegments() << 15;
|
||||
}
|
||||
@@ -446,21 +452,19 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures if the maps created by this builder should return {@code null} instead of previous
|
||||
* mapped values on {@link ChronicleMap#put(Object, Object) ChornicleMap.put(key, value)}
|
||||
* calls.
|
||||
* Configures if the maps created by this builder should return {@code null} instead of previous mapped values on
|
||||
* {@link ChronicleMap#put(Object, Object) ChornicleMap.put(key, value)} calls.
|
||||
*
|
||||
* <p>{@link Map#put(Object, Object) Map.put()} returns the previous value, functionality which
|
||||
* is rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection,
|
||||
* it has to create a new object and deserialize the data from off-heap memory. It's expensive
|
||||
* for something you probably don't use.
|
||||
* <p>{@link Map#put(Object, Object) Map.put()} returns the previous value, functionality which is rarely used but
|
||||
* fairly cheap for {@link HashMap}. In the case, for an off heap collection, it has to create a new object and
|
||||
* deserialize the data from off-heap memory. It's expensive for something you probably don't use.
|
||||
* </p>
|
||||
* <p>By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract and returns the previous
|
||||
* mapped value on {@code put()} calls.
|
||||
* </p>
|
||||
*
|
||||
* <p>By default, of cause, {@code ChronicleMap} conforms the general {@code Map} contract and
|
||||
* returns the previous mapped value on {@code put()} calls.
|
||||
*
|
||||
* @param putReturnsNull {@code true} if you want {@link ChronicleMap#put(Object, Object)
|
||||
* ChronicleMap.put()} to not return the value that was replaced but
|
||||
* instead return {@code null}
|
||||
* @param putReturnsNull {@code true} if you want {@link ChronicleMap#put(Object, Object) ChronicleMap.put()} to not
|
||||
* return the value that was replaced but instead return {@code null}
|
||||
* @return an instance of the map builder back
|
||||
* @see #removeReturnsNull(boolean)
|
||||
*/
|
||||
@@ -474,17 +478,16 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures if the maps created by this builder should return {@code null} instead of the last
|
||||
* mapped value on {@link ChronicleMap#remove(Object) ChronicleMap.remove(key)} calls.
|
||||
* Configures if the maps created by this builder should return {@code null} instead of the last mapped value on
|
||||
* {@link ChronicleMap#remove(Object) ChronicleMap.remove(key)} calls.
|
||||
*
|
||||
* <p>{@link Map#remove(Object) Map.remove()} returns the previous value, functionality which is
|
||||
* rarely used but fairly cheap for {@link HashMap}. In the case, for an off heap collection, it
|
||||
* has to create a new object and deserialize the data from off-heap memory. It's expensive for
|
||||
* something you probably don't use.
|
||||
* <p>{@link Map#remove(Object) Map.remove()} returns the previous value, functionality which is rarely used but
|
||||
* fairly cheap for {@link HashMap}. In the case, for an off heap collection, it has to create a new object and
|
||||
* deserialize the data from off-heap memory. It's expensive for something you probably don't use.
|
||||
* </p>
|
||||
*
|
||||
* @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object)
|
||||
* ChronicleMap.remove()} to not return the value of the removed entry
|
||||
* but instead return {@code null}
|
||||
* @param removeReturnsNull {@code true} if you want {@link ChronicleMap#remove(Object) ChronicleMap.remove()} to
|
||||
* not return the value of the removed entry but instead return {@code null}
|
||||
* @return an instance of the map builder back
|
||||
* @see #putReturnsNull(boolean)
|
||||
*/
|
||||
@@ -505,7 +508,6 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
return self();
|
||||
}
|
||||
|
||||
|
||||
int metaDataBytes() {
|
||||
return metaDataBytes;
|
||||
}
|
||||
@@ -540,14 +542,6 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
'}';
|
||||
}
|
||||
|
||||
private static String pretty(int value) {
|
||||
return value > 0 ? value + "" : "not configured";
|
||||
}
|
||||
|
||||
private static String pretty(Object obj) {
|
||||
return obj != null ? obj + "" : "not configured";
|
||||
}
|
||||
|
||||
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
@@ -668,19 +662,18 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures factory which is used to create a new value instance, if value class is either
|
||||
* {@link Byteable}, {@link BytesMarshallable} or {@link Externalizable} subclass in maps,
|
||||
* created by this builder.
|
||||
* Configures factory which is used to create a new value instance, if value class is either {@link Byteable},
|
||||
* {@link BytesMarshallable} or {@link Externalizable} subclass in maps, created by this builder.
|
||||
*
|
||||
* <p>Default value deserialization factory is {@link NewInstanceObjectFactory}, which creates
|
||||
* a new value instance using {@link Class#newInstance()} default constructor. You could provide
|
||||
* an {@link AllocateInstanceObjectFactory}, which uses {@code Unsafe.allocateInstance(Class)}
|
||||
* (you might want to do this for better performance or if you don't want to initialize fields),
|
||||
* or a factory which calls a value class constructor with some arguments, or a factory which
|
||||
* internally delegates to instance pool or {@link ThreadLocal}, to reduce allocations.
|
||||
* <p>Default value deserialization factory is {@link NewInstanceObjectFactory}, which creates a new value instance
|
||||
* using {@link Class#newInstance()} default constructor. You could provide an {@link
|
||||
* AllocateInstanceObjectFactory}, which uses {@code Unsafe.allocateInstance(Class)} (you might want to do this for
|
||||
* better performance or if you don't want to initialize fields), or a factory which calls a value class constructor
|
||||
* with some arguments, or a factory which internally delegates to instance pool or {@link ThreadLocal}, to reduce
|
||||
* allocations.
|
||||
* </p>
|
||||
*
|
||||
* @param valueDeserializationFactory the value factory used to produce instances to deserialize
|
||||
* data in
|
||||
* @param valueDeserializationFactory the value factory used to produce instances to deserialize data in
|
||||
* @return this builder back
|
||||
*/
|
||||
public B valueDeserializationFactory(@NotNull ObjectFactory<V> valueDeserializationFactory) {
|
||||
@@ -698,14 +691,14 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the value to be put for each key queried in {@link ChronicleMap#acquireUsing
|
||||
* acquireUsing()} method, if the key is absent in the map, created by this builder.
|
||||
* Specifies the value to be put for each key queried in {@link ChronicleMap#acquireUsing acquireUsing()} method, if
|
||||
* the key is absent in the map, created by this builder.
|
||||
*
|
||||
* <p>This configuration overrides any previous {@link #defaultValueProvider(
|
||||
* DefaultValueProvider)} configuration to this {@code AbstractChronicleMapBuilder}.
|
||||
* <p>This configuration overrides any previous {@link #defaultValueProvider(DefaultValueProvider)} configuration
|
||||
* to this {@code AbstractChronicleMapBuilder}.
|
||||
* </p>
|
||||
*
|
||||
* @param defaultValue the default value to be put to the map for absent keys during {@code
|
||||
* acquireUsing()} calls
|
||||
* @param defaultValue the default value to be put to the map for absent keys during {@code acquireUsing()} calls
|
||||
* @return this builder object back
|
||||
*/
|
||||
public B defaultValue(V defaultValue) {
|
||||
@@ -718,11 +711,12 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the function to obtain a value for the key during {@link ChronicleMap#acquireUsing
|
||||
* acquireUsing()} calls, if the key is absent in the map, created by this builder.
|
||||
* Specifies the function to obtain a value for the key during {@link ChronicleMap#acquireUsing acquireUsing()}
|
||||
* calls, if the key is absent in the map, created by this builder.
|
||||
*
|
||||
* <p>This configuration overrides any previous {@link #defaultValue(Object)} configuration to
|
||||
* this {@code AbstractChronicleMapBuilder}.
|
||||
* <p>This configuration overrides any previous {@link #defaultValue(Object)} configuration to this {@code
|
||||
* AbstractChronicleMapBuilder}.
|
||||
* </p>
|
||||
*
|
||||
* @param defaultValueProvider the strategy to obtain a default value by the absent key
|
||||
* @return this builder object back
|
||||
@@ -876,7 +870,7 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
valueBuilder.constantSizeBySample(sampleValue);
|
||||
}
|
||||
|
||||
private ChronicleMap<K, V> establishReplication(VanillaChronicleMap<K, ?, ?, V, ?, ?> map)
|
||||
private ChronicleMap<K, V> establishReplication(VanillaChronicleMap<K, ?, ?, V, ?, ?> map)
|
||||
throws IOException {
|
||||
if (map instanceof ReplicatedChronicleMap) {
|
||||
ReplicatedChronicleMap result = (ReplicatedChronicleMap) map;
|
||||
@@ -919,5 +913,11 @@ public abstract class AbstractChronicleMapBuilder<K, V,
|
||||
public byte identifier() {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public int segmentHeaderSize() {
|
||||
int segments = actualSegments();
|
||||
// reduce false sharing unless we have alot of segments.
|
||||
return segments > 8192 ? 16 : segments > 1024 ? 32 : 64;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user