diff --git a/README.md b/README.md index 261b69a2c..656211b5c 100644 --- a/README.md +++ b/README.md @@ -1964,10 +1964,14 @@ class GrowOnlySetValuedMapRemoteOperations Usage: ``` +HashSet averageValue = new HashSet<>(); +for (int i = 0; i < AVERAGE_SET_SIZE; i++) { + averageValue.add(i); +} ChronicleMap> map1 = ChronicleMapBuilder .of(Integer.class, (Class>) (Class) Set.class) .entries(100) - .averageValueSize(1000) + .averageValue(averageValue) .entryOperations(growOnlySetValuedMapEntryOperations()) .remoteOperations(growOnlySetValuedMapRemoteOperations()) .replication((byte) 1, /* ... replicated nodes */) @@ -2110,10 +2114,14 @@ private static boolean innerRemoveEdge( Usage: ```java +HashSet averageValue = new HashSet<>(); +for (int i = 0; i < AVERAGE_CONNECTIVITY; i++) { + averageValue.add(i); +} ChronicleMap> graph = ChronicleMapBuilder .of(Integer.class, (Class>) (Class) Set.class) .entries(100) - .averageValueSize(1000) + .averageValue(averageValue) .create(); addEdge(graph, 1, 2); diff --git a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java index c8602a876..6bc3f881f 100755 --- a/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/ChronicleHashBuilder.java @@ -86,7 +86,8 @@ public interface ChronicleHashBuilder, /** * Configures the average number of bytes, taken by serialized form of keys, put into hash - * containers, created by this builder. If key size is always the same, call {@link + * containers, created by this builder. However, in many cases {@link #averageKey(Object)} might + * be easier to use and more reliable. If key size is always the same, call {@link * #constantKeySizeBySample(Object)} method instead of this one. * *

{@code ChronicleHashBuilder} implementation heuristically chooses @@ -104,11 +105,43 @@ public interface ChronicleHashBuilder, * @throws IllegalStateException if key size is known statically and shouldn't be configured * by user * @throws IllegalArgumentException if the given {@code keySize} is non-positive + * @see #averageKey(Object) * @see #constantKeySizeBySample(Object) * @see #actualChunkSize(int) */ B averageKeySize(double averageKeySize); + /** + * Configures the average number of bytes, taken by serialized form of keys, put into hash + * containers, created by this builder, by serializing the given {@code averageKey} using + * the configured {@link #keyMarshallers(BytesWriter, BytesReader) keys marshallers}. + * In some cases, {@link #averageKeySize(double)} might be easier to use, than constructing the + * "average key". If key size is always the same, call {@link #constantKeySizeBySample( + * Object)} method instead of this one. + * + *

{@code ChronicleHashBuilder} implementation heuristically chooses + * {@linkplain #actualChunkSize(int) the actual chunk size} based on this configuration, that, + * however, might result to quite high internal fragmentation, i. e. losses because only + * integral number of chunks could be allocated for the entry. If you want to avoid this, you + * should manually configure the actual chunk size in addition to this average key size + * configuration, which is anyway needed. + * + *

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. + * + *

Calling this method clears any previous {@link #constantKeySizeBySample(Object)} + * configuration. + * + * @param averageKey the average (by footprint in serialized form) key, is going to be put + * into the hash containers, created by this builder + * @return this builder back + * @throws NullPointerException if the given {@code averageKey} is {@code null} + * @see #averageKeySize(double) + * @see #constantKeySizeBySample(Object) + * @see #actualChunkSize(int) + */ + B averageKey(K averageKey); + /** * Configures the constant number of bytes, taken by serialized form of keys, put into hash * containers, created by this builder. This is done by providing the {@code sampleKey}, all @@ -120,6 +153,8 @@ public interface ChronicleHashBuilder, *

If key size varies, method {@link #averageKeySize(double)} should be called instead of * this one. * + *

Calling this method clears any previous {@link #averageKey(Object)} configuration. + * * @param sampleKey the sample key * @return this builder back * @see #averageKeySize(double) diff --git a/src/main/java/net/openhft/chronicle/hash/serialization/internal/SerializationBuilder.java b/src/main/java/net/openhft/chronicle/hash/serialization/internal/SerializationBuilder.java index bf745198f..43f3968cb 100755 --- a/src/main/java/net/openhft/chronicle/hash/serialization/internal/SerializationBuilder.java +++ b/src/main/java/net/openhft/chronicle/hash/serialization/internal/SerializationBuilder.java @@ -257,7 +257,7 @@ public final class SerializationBuilder implements Cloneable, Serializable { return this; } - public SerializationBuilder constantSizeBySample(E sampleObject) { + public long serializationSize(E sampleObject) { Object originalInterop = this.interop; Provider interopProvider = Provider.of(originalInterop.getClass()); ThreadLocalCopies copies = interopProvider.getCopies(null); @@ -286,7 +286,11 @@ public final class SerializationBuilder implements Cloneable, Serializable { MetaProvider metaInteropProvider = this.metaInteropProvider; metaInterop = metaInteropProvider.get(copies, this.metaInterop, interop, sampleObject); } - long constantSize = metaInterop.size(interop, sampleObject); + return metaInterop.size(interop, sampleObject); + } + + public SerializationBuilder constantSizeBySample(E sampleObject) { + long constantSize = serializationSize(sampleObject); if (sizeIsStaticallyKnown) { long expectedConstantSize = pseudoReadConstantSize(); if (constantSize != expectedConstantSize) { diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index c450933a5..c58eed9c5 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -141,8 +141,10 @@ public final class ChronicleMapBuilder implements private long entriesPerSegment = -1L; private long actualChunksPerSegment = -1L; private double averageKeySize = UNDEFINED_DOUBLE_CONFIG; + private K averageKey; private K sampleKey; private double averageValueSize = UNDEFINED_DOUBLE_CONFIG; + private V averageValue; private V sampleValue; private int actualChunkSize = 0; private int maxChunksPerEntry = -1; @@ -265,6 +267,7 @@ public final class ChronicleMapBuilder implements * @param averageKeySize the average size of the key * @throws IllegalStateException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} + * @see #averageKey(Object) * @see #constantKeySizeBySample(Object) * @see #averageValueSize(double) * @see #actualChunkSize(int) @@ -277,6 +280,25 @@ public final class ChronicleMapBuilder implements return this; } + /** + * {@inheritDoc} + * + * @param averageKey the average (by footprint in serialized form) key, is going to be put + * into the hash containers, created by this builder + * @throws NullPointerException {@inheritDoc} + * @see #averageKeySize(double) + * @see #constantKeySizeBySample(Object) + * @see #averageValue(Object) + * @see #actualChunkSize(int) + */ + @Override + public ChronicleMapBuilder averageKey(K averageKey) { + checkSizeIsNotStaticallyKnown(keyBuilder); + this.averageKey = averageKey; + sampleKey = null; + return this; + } + /** * {@inheritDoc} * @@ -287,22 +309,25 @@ public final class ChronicleMapBuilder implements * .immutableKeys() * .create();} * - * @see ChronicleHashBuilder#averageKeySize(double) + * @see #averageKeySize(double) + * @see #averageKey(Object) * @see #constantValueSizeBySample(Object) */ @Override public ChronicleMapBuilder constantKeySizeBySample(K sampleKey) { this.sampleKey = sampleKey; + averageKey = null; return this; } private double averageKeySize() { - return averageKeyOrValueSize(averageKeySize, keyBuilder); + return averageKeyOrValueSize(averageKeySize, keyBuilder, averageKey); } /** * Configures the average 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 + * created by this builder. However, in many cases {@link #averageValue(Object)} might be easier + * to use and more reliable. If value size is always the same, call {@link * #constantValueSizeBySample(Object)} method instead of this one. * *

{@code ChronicleHashBuilder} implementation heuristically chooses {@linkplain @@ -320,8 +345,9 @@ public final class ChronicleMapBuilder implements * @throws IllegalStateException if value size is known statically and shouldn't be * configured by user * @throws IllegalArgumentException if the given {@code averageValueSize} is non-positive + * @see #averageValue(Object) * @see #constantValueSizeBySample(Object) - * @see ChronicleHashBuilder#averageKeySize(double) + * @see #averageKeySize(double) * @see #actualChunkSize(int) */ public ChronicleMapBuilder averageValueSize(double averageValueSize) { @@ -331,6 +357,46 @@ public final class ChronicleMapBuilder implements return this; } + /** + * Configures the average number of bytes, taken by serialized form of values, put into maps, + * created by this builder, by serializing the given {@code averageValue} using the configured + * {@link #valueMarshallers(BytesWriter, BytesReader) value marshallers}. In some cases, {@link + * #averageValueSize(double)} might be easier to use, than constructing the "average value". + * If value size is always the same, call {@link #constantValueSizeBySample(Object)} method + * instead of this one. + * + *

Example: If you + * + *

{@code ChronicleHashBuilder} implementation heuristically chooses {@linkplain + * #actualChunkSize(int) the actual chunk size} based on this configuration and the key size, + * that, however, might result to quite high internal fragmentation, i. e. losses because only + * integral number of chunks could be allocated for the entry. If you want to avoid this, you + * should manually configure the actual chunk size in addition to this average value size + * configuration, which is anyway needed. + * + *

If values are of 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. + * + *

Calling this method clears any previous {@link #constantValueSizeBySample(Object)} + * configuration. + * + * @param averageValue the average (by footprint in serialized form) value, is going to be put + * into the maps, created by this builder + * @return this builder back + * @throws NullPointerException if the given {@code averageValue} is {@code null} + * @see #averageValueSize(double) + * @see #constantValueSizeBySample(Object) + * @see #averageKey(Object) + * @see #actualChunkSize(int) + */ + public ChronicleMapBuilder averageValue(V averageValue) { + Objects.requireNonNull(averageValue); + checkSizeIsNotStaticallyKnown(valueBuilder); + this.averageValue = averageValue; + sampleValue = null; + return this; + } + private static void checkAverageSize(double averageSize, String role) { if (averageSize <= 0 || Double.isNaN(averageSize) || Double.isInfinite(averageSize)) { @@ -353,24 +419,33 @@ public final class ChronicleMapBuilder implements *

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. * - *

If value size varies, method {@link #averageValueSize(double)} should be called instead of - * this one. + *

If value size varies, method {@link #averageValue(Object)} or {@link + * #averageValueSize(double)} should be called instead of this one. + * + *

Calling this method clears any previous {@link #averageValue(Object)} configuration. * * @param sampleValue the sample value * @return this builder back * @see #averageValueSize(double) + * @see #averageValue(Object) * @see #constantKeySizeBySample(Object) */ public ChronicleMapBuilder constantValueSizeBySample(V sampleValue) { this.sampleValue = sampleValue; + averageValue = null; return this; } double averageValueSize() { - return averageKeyOrValueSize(averageValueSize, valueBuilder); + return averageKeyOrValueSize(averageValueSize, valueBuilder, averageValue); } - private double averageKeyOrValueSize(double configuredSize, SerializationBuilder builder) { + private double averageKeyOrValueSize( + double configuredSize, SerializationBuilder builder, Object average) { + if (average != null) { + builder.maxSize(DEFAULT_KEY_OR_VALUE_SIZE); + return builder.serializationSize(average); + } if (isDefined(configuredSize)) return configuredSize; if (builder.constantSizeMarshaller()) @@ -1267,9 +1342,9 @@ public final class ChronicleMapBuilder implements } } - ChronicleMap createWithFile(File file, SingleChronicleHashReplication singleHashReplication, - ReplicationChannel channel) throws IOException { - // pushingToMapEventListener(); + ChronicleMap createWithFile( + File file, SingleChronicleHashReplication singleHashReplication, + ReplicationChannel channel) throws IOException { for (int i = 0; i < 10; i++) { if (file.exists() && file.length() > 0) { try (FileInputStream fis = new FileInputStream(file); diff --git a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java index c4ca226c5..9d2066526 100755 --- a/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java +++ b/src/main/java/net/openhft/chronicle/set/ChronicleSetBuilder.java @@ -123,6 +123,12 @@ public final class ChronicleSetBuilder return this; } + @Override + public ChronicleSetBuilder averageKey(K averageKey) { + chronicleMapBuilder.averageKey(averageKey); + return this; + } + /** * {@inheritDoc} * diff --git a/src/test/java/net/openhft/chronicle/map/NestedContextsTest.java b/src/test/java/net/openhft/chronicle/map/NestedContextsTest.java index 2292cefde..71933934d 100644 --- a/src/test/java/net/openhft/chronicle/map/NestedContextsTest.java +++ b/src/test/java/net/openhft/chronicle/map/NestedContextsTest.java @@ -33,8 +33,13 @@ public class NestedContextsTest { @Test public void nestedContextsTest() throws ExecutionException, InterruptedException { + HashSet averageValue = new HashSet<>(); + for (int i = 0; i < 5; i++) { + averageValue.add(i); + } ChronicleMap> graph = ChronicleMapBuilder .of(Integer.class, (Class>) (Class) Set.class) + .averageValue(averageValue) .actualSegments(2) .create(); diff --git a/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakCrdtTest.java b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakCrdtTest.java index 3d32a578c..936f984b0 100644 --- a/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakCrdtTest.java +++ b/src/test/java/net/openhft/chronicle/map/TcpReplicationSoakCrdtTest.java @@ -100,10 +100,14 @@ public class TcpReplicationSoakCrdtTest { public void setup() throws IOException { final InetSocketAddress endpoint = new InetSocketAddress("localhost", s_port + 1); + HashSet averageValue = new HashSet<>(); + for (int i = 0; i < 10; i++) { + averageValue.add(i); + } ChronicleMapBuilder> builder = ChronicleMapBuilder .of(Integer.class, (Class>) (Class) Set.class) .entries(Builder.SIZE) - .averageValueSize(3000) // more than HashSet of 10 Integers on average, serialized + .averageValue(averageValue) .entryOperations(growOnlySetValuedMapEntryOperations()) .remoteOperations(growOnlySetValuedMapRemoteOperations());