mirror of
https://github.com/OpenHFT/Chronicle-Map.git
synced 2021-09-13 01:26:12 +00:00
Added ChronicleMapBuilder.constantKey[Value]SizeBySample() methods
This commit is contained in:
@@ -58,7 +58,9 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
// used when reading the number of entries per
|
||||
private int actualEntriesPerSegment = -1;
|
||||
private int keySize = 0;
|
||||
private K sampleKey;
|
||||
private int valueSize = 0;
|
||||
private V sampleValue;
|
||||
private int entrySize = 0;
|
||||
private Alignment alignment = Alignment.OF_4_BYTES;
|
||||
private long entries = 1 << 20;
|
||||
@@ -133,11 +135,9 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the number of bytes, taken by serialized form of keys, put into maps, created by
|
||||
* this builder.
|
||||
*
|
||||
* <p>If key is a boxed primitive type or {@link Byteable} subclass, i. e. if key size is known
|
||||
* statically, it is automatically accounted and shouldn't be specified by user.
|
||||
* Configures the optimal number of bytes, taken by serialized form of keys, put into maps,
|
||||
* created by this builder. If key size is always the same, call
|
||||
* {@link #constantKeySizeBySample(Object)} method instead of this one.
|
||||
*
|
||||
* <p>If key size varies moderately, specify the size higher than average, but lower than
|
||||
* the maximum possible, to minimize average memory overuse. If key size varies in a wide range,
|
||||
@@ -157,6 +157,7 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
*
|
||||
* @param keySize number of bytes, taken by serialized form of keys
|
||||
* @return this {@code ChronicleMapBuilder} back
|
||||
* @see #constantKeySizeBySample(Object)
|
||||
* @see #valueSize(int)
|
||||
* @see #entrySize()
|
||||
*/
|
||||
@@ -167,13 +168,42 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the constant number of bytes, taken by serialized form of keys, put into maps,
|
||||
* created by this builder. This is done by providing the {@code sampleKey}, all keys should
|
||||
* take the same number of bytes in serialized form, as this sample object.
|
||||
*
|
||||
* <p>For example, if your keys are Git commit hashes:<pre>{@code
|
||||
* Map<byte[], String> gitCommitMessagesByHash =
|
||||
* ChronicleMapBuilder.of(byte[].class, String.class)
|
||||
* .constantKeySizeBySample(new byte[20])
|
||||
* .create();
|
||||
* }</pre>
|
||||
*
|
||||
* <p>If keys are of boxed primitive type or {@link Byteable} subclass, i. e. if key size
|
||||
* is known statically, it is automatically accounted and this method shouldn't be called.
|
||||
*
|
||||
* <p>If key size varies, method {@link #keySize(int)} or {@link #entrySize(int)} should be
|
||||
* called instead of this one.
|
||||
*
|
||||
* @param sampleKey the sample key
|
||||
* @return this builder back
|
||||
* @see #keySize(int)
|
||||
* @see #constantValueSizeBySample(Object)
|
||||
*/
|
||||
public ChronicleMapBuilder<K, V> constantKeySizeBySample(K sampleKey) {
|
||||
this.sampleKey = sampleKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
private int keySize() {
|
||||
return keyOrValueSize(keySize, keyBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the number of bytes, taken by serialized form of value, put into maps, created by
|
||||
* this builder.
|
||||
* Configures the optimal number of bytes, taken by serialized form of values, put into maps,
|
||||
* created by this builder. If value size is always the same, call
|
||||
* {@link #constantValueSizeBySample(Object)} method instead of this one.
|
||||
*
|
||||
* <p>If value is a boxed primitive type or {@link Byteable} subclass, i. e. if value size
|
||||
* is known statically, it is automatically accounted and shouldn't be specified by user.
|
||||
@@ -195,6 +225,27 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the constant number of bytes, taken by serialized form of values, put into maps,
|
||||
* created by this builder. This is done by providing the {@code sampleValue}, all values should
|
||||
* take the same number of bytes in serialized form, as this sample object.
|
||||
*
|
||||
* <p>If values are of boxed primitive type or {@link Byteable} subclass, i. e. if value size
|
||||
* is known statically, it is automatically accounted and this method shouldn't be called.
|
||||
*
|
||||
* <p>If value size varies, method {@link #valueSize(int)} or {@link #entrySize(int)} should be
|
||||
* called instead of this one.
|
||||
*
|
||||
* @param sampleValue the sample value
|
||||
* @return this builder back
|
||||
* @see #valueSize(int)
|
||||
* @see #constantKeySizeBySample(Object)
|
||||
*/
|
||||
public ChronicleMapBuilder<K, V> constantValueSizeBySample(V sampleValue) {
|
||||
this.sampleValue = sampleValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
private int valueSize() {
|
||||
return keyOrValueSize(valueSize, valueBuilder);
|
||||
}
|
||||
@@ -252,9 +303,10 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
* implementations, with respect to {@linkplain #entryAndValueAlignment() alignment}.
|
||||
*
|
||||
* <p>There are three major patterns of this configuration usage:
|
||||
* <ul>
|
||||
* <li>Key and value sizes are known exactly. Just specify them using precisely
|
||||
* corresponding methods. No memory would be wasted in this case.</li>
|
||||
* <ol>
|
||||
* <li>Key and value sizes are constant. Configure them via {@link #constantKeySizeBySample(
|
||||
* Object)} and {@link #constantValueSizeBySample(Object)} methods, and you will experience
|
||||
* no memory waste at all.</li>
|
||||
* <li>Key and/or value size varies moderately. Specify them using corresponding methods,
|
||||
* or {@linkplain #entrySize(int) specify entry size} directly, by values somewhere between
|
||||
* average and maximum possible. The idea is to have most (90% or more) entries to fit
|
||||
@@ -265,12 +317,18 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
* from 5 to several dozens of "chunks". With this approach, average memory waste should be
|
||||
* very low.
|
||||
*
|
||||
* <p>However, remember that operations with entries that span several "entry sizes"
|
||||
* are a bit slower, than entries which take a single "entry size" (that is why "chunk"
|
||||
* approach is not recommended, when key and/or value size varies moderately). Also, note
|
||||
* that the maximum number of chunks could be taken by an entry is 64.
|
||||
* {@link IllegalArgumentException} is thrown on attempt to insert too large entry, compared
|
||||
* to the configured or computed entry size.
|
||||
* <p>However, remember that
|
||||
* <ul>
|
||||
* <li>Operations with entries that span several "entry sizes" are a bit slower, than
|
||||
* entries which take a single "entry size". That is why "chunk" approach
|
||||
* is not recommended, when key and/or value size varies moderately.</li>
|
||||
* <li>The maximum number of chunks could be taken by an entry is 64.
|
||||
* {@link IllegalArgumentException} is thrown on attempt to insert too large entry,
|
||||
* compared to the configured or computed entry size.</li>
|
||||
* <li>The number of "entries" {@linkplain #entries(long) in the whole map} and
|
||||
* {@linkplain #actualEntriesPerSegment() per segment} is actually the number
|
||||
* "chunks".</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Example: if values in your map are adjacency lists of some social graph, where nodes
|
||||
* are represented as {@code long} ids, and adjacency lists are serialized in efficient
|
||||
@@ -279,16 +337,19 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
* be a good choice: <pre>{@code
|
||||
* Map<Long, long[]> socialGraph = ChronicleMapBuilder
|
||||
* .of(Long.class, long[].class)
|
||||
* .entries(1_000_000_000)
|
||||
* // given that graph should have of 1 billion nodes, and 150 average adjacency list size
|
||||
* // => values takes 3 chuncks on average
|
||||
* .entries(1_000_000_000L * (150 / 50))
|
||||
* .entrySize(50 * 8)
|
||||
* .create();}</pre>
|
||||
* It is minimum possible (because 3000 friends / 50 friends = 60 is close to 64 "max
|
||||
* chunks by single entry" limit, and ensures moderate average memory overuse (not more
|
||||
* than 20%).
|
||||
* </li>
|
||||
* </ul>
|
||||
* </ol>
|
||||
*
|
||||
* @return size of memory allocation unit (in bytes)
|
||||
* @see #entries(long)
|
||||
*/
|
||||
public int entrySize() {
|
||||
if (entrySize > 0)
|
||||
@@ -789,6 +850,11 @@ public class ChronicleMapBuilder<K, V> implements Cloneable {
|
||||
int maxSize = entrySize() * figureBufferAllocationFactor();
|
||||
keyBuilder.maxSize(maxSize);
|
||||
valueBuilder.maxSize(maxSize);
|
||||
|
||||
if (sampleKey != null)
|
||||
keyBuilder.constantSizeBySample(sampleKey);
|
||||
if (sampleValue != null)
|
||||
valueBuilder.constantSizeBySample(sampleValue);
|
||||
}
|
||||
|
||||
private ChronicleMap<K, V> establishReplication(ChronicleMap<K, V> map)
|
||||
|
||||
@@ -18,6 +18,8 @@ package net.openhft.chronicle.map;
|
||||
|
||||
import net.openhft.chronicle.map.serialization.*;
|
||||
import net.openhft.chronicle.map.serialization.impl.*;
|
||||
import net.openhft.chronicle.map.threadlocal.Provider;
|
||||
import net.openhft.chronicle.map.threadlocal.ThreadLocalCopies;
|
||||
import net.openhft.lang.io.Bytes;
|
||||
import net.openhft.lang.io.serialization.*;
|
||||
import net.openhft.lang.io.serialization.impl.*;
|
||||
@@ -61,6 +63,7 @@ final class SerializationBuilder<E> implements Cloneable {
|
||||
private CopyingInterop copyingInterop = null;
|
||||
private MetaBytesInterop<E, ?> metaInterop;
|
||||
private MetaProvider<E, ?, ?> metaInteropProvider;
|
||||
private long maxSize;
|
||||
private ObjectFactory<E> factory;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -132,7 +135,6 @@ final class SerializationBuilder<E> implements Cloneable {
|
||||
|
||||
public SerializationBuilder<E> writer(BytesWriter<E> writer) {
|
||||
return copyingInterop(CopyingInterop.FROM_WRITER)
|
||||
.sizeMarshaller(stopBit())
|
||||
.setInterop(writer)
|
||||
.metaInterop(CopyingMetaBytesInterop
|
||||
.<E, BytesWriter<E>>forBytesWriter(bufferIdentity));
|
||||
@@ -142,7 +144,6 @@ final class SerializationBuilder<E> implements Cloneable {
|
||||
ObjectFactory<E> factory) {
|
||||
if (factory == null) factory = this.factory;
|
||||
return copyingInterop(CopyingInterop.FROM_MARSHALLER)
|
||||
.sizeMarshaller(stopBit())
|
||||
.reader(BytesReaders.fromBytesMarshaller(marshaller), factory)
|
||||
.setInterop(marshaller)
|
||||
.metaInterop(CopyingMetaBytesInterop
|
||||
@@ -151,16 +152,57 @@ final class SerializationBuilder<E> implements Cloneable {
|
||||
|
||||
public SerializationBuilder<E> maxSize(long maxSize) {
|
||||
if (copyingInterop == CopyingInterop.FROM_MARSHALLER) {
|
||||
this.maxSize = maxSize;
|
||||
metaInteropProvider(CopyingMetaBytesInterop
|
||||
.<E, BytesMarshaller<E>>providerForBytesMarshaller(
|
||||
instancesAreMutable, maxSize));
|
||||
} else if (copyingInterop == CopyingInterop.FROM_WRITER) {
|
||||
this.maxSize = maxSize;
|
||||
metaInteropProvider(CopyingMetaBytesInterop
|
||||
.<E, BytesWriter<E>>providerForBytesWriter(instancesAreMutable, maxSize));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public SerializationBuilder<E> constantSizeBySample(E sampleObject) {
|
||||
Object originalInterop = this.interop;
|
||||
Provider interopProvider = Provider.of(originalInterop.getClass());
|
||||
ThreadLocalCopies copies = interopProvider.getCopies(null);
|
||||
Object interop = interopProvider.get(copies, originalInterop);
|
||||
copies = metaInteropProvider.getCopies(copies);
|
||||
MetaBytesWriter metaInterop;
|
||||
if (maxSize > 0) {
|
||||
// this loop is very dumb: tries to find buffer size, sufficient to serialize
|
||||
// the object, by trying to serialize to small buffers first and doubling the buffer
|
||||
// size on _any_ Exception (it may be IllegalArgument, IndexOutOfBounds, IllegalState,
|
||||
// see JLANG-17 issue).
|
||||
// TODO The question is: couldn't this cause JVM crash without throwing an exception?
|
||||
findSufficientSerializationSize:
|
||||
while (true) {
|
||||
MetaProvider metaInteropProvider = this.metaInteropProvider;
|
||||
try {
|
||||
metaInterop = metaInteropProvider.get(
|
||||
copies, this.metaInterop, interop, sampleObject);
|
||||
break findSufficientSerializationSize;
|
||||
} catch (Exception e) {
|
||||
// assuming nobody need > 512 mb _constant size_ keys/values
|
||||
if (maxSize > 512 * 1024 * 1024) {
|
||||
throw e;
|
||||
}
|
||||
maxSize(maxSize * 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MetaProvider metaInteropProvider = this.metaInteropProvider;
|
||||
metaInterop = metaInteropProvider.get(copies, this.metaInterop, interop, sampleObject);
|
||||
}
|
||||
long constantSize = metaInterop.size(interop, sampleObject);
|
||||
// set max size once more, because current maxSize could be x64 or x2 larger than needed
|
||||
maxSize(constantSize);
|
||||
sizeMarshaller(SizeMarshallers.constant(constantSize));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SerializationBuilder<E> objectSerializer(ObjectSerializer serializer) {
|
||||
if (reader == null || interop == null) {
|
||||
ObjectFactory<E> factory = this.factory;
|
||||
|
||||
@@ -47,5 +47,34 @@ public final class SizeMarshallers {
|
||||
}
|
||||
}
|
||||
|
||||
public static SizeMarshaller constant(long size) {
|
||||
return new ConstantSizeMarshaller(size);
|
||||
}
|
||||
|
||||
private static class ConstantSizeMarshaller implements SizeMarshaller {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
private final long size;
|
||||
|
||||
private ConstantSizeMarshaller(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int sizeEncodingSize(long size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSize(Bytes bytes, long size) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readSize(Bytes bytes) {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
private SizeMarshallers() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user