diff options
-rw-r--r-- | subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java | 6 | ||||
-rw-r--r-- | subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java | 60 |
2 files changed, 52 insertions, 14 deletions
diff --git a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java index 988ce423..24ec3b59 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java +++ b/subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java | |||
@@ -41,7 +41,9 @@ public class TupleHashProvider implements ContinousHashProvider<Tuple> { | |||
41 | 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, | 41 | 3643, 3659, 3671, 3673, 3677, 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, |
42 | 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911 }; | 42 | 3797, 3803, 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911 }; |
43 | 43 | ||
44 | protected static final long LARGESTPRIME30BITS = 1073741789; | 44 | public static final long LARGEST_PRIME_30_BITS = 1073741789; |
45 | |||
46 | public static final int MAX_MODEL_SIZE = (int) LARGEST_PRIME_30_BITS; | ||
45 | 47 | ||
46 | public TupleHashProvider() { | 48 | public TupleHashProvider() { |
47 | if (primes.length < MAX_PRACTICAL_DEPTH) { | 49 | if (primes.length < MAX_PRACTICAL_DEPTH) { |
@@ -58,7 +60,7 @@ public class TupleHashProvider implements ContinousHashProvider<Tuple> { | |||
58 | long accumulator = 0; | 60 | long accumulator = 0; |
59 | final int prime = primes[index]; | 61 | final int prime = primes[index]; |
60 | for (int i = 0; i < key.getSize(); i++) { | 62 | for (int i = 0; i < key.getSize(); i++) { |
61 | accumulator = (prime * accumulator + key.get(i)) % LARGESTPRIME30BITS; | 63 | accumulator = (prime * accumulator + key.get(i)) % MAX_MODEL_SIZE; |
62 | } | 64 | } |
63 | 65 | ||
64 | return (int) accumulator; | 66 | return (int) accumulator; |
diff --git a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java index 95907ee1..07380966 100644 --- a/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java +++ b/subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java | |||
@@ -1,6 +1,8 @@ | |||
1 | package tools.refinery.store.tuple; | 1 | package tools.refinery.store.tuple; |
2 | 2 | ||
3 | import java.util.ArrayList; | 3 | import tools.refinery.store.model.TupleHashProvider; |
4 | |||
5 | import java.util.Arrays; | ||
4 | 6 | ||
5 | public record Tuple1(int value0) implements Tuple { | 7 | public record Tuple1(int value0) implements Tuple { |
6 | @Override | 8 | @Override |
@@ -26,29 +28,63 @@ public record Tuple1(int value0) implements Tuple { | |||
26 | return "[" + value0 + "]"; | 28 | return "[" + value0 + "]"; |
27 | } | 29 | } |
28 | 30 | ||
31 | /** | ||
32 | * This class uses safe double-checked locking, see | ||
33 | * <a href="https://shipilev.net/blog/2014/safe-public-construction/">Safe Publication and Safe Initialization in | ||
34 | * Java</a> for details. | ||
35 | */ | ||
29 | public static class Cache { | 36 | public static class Cache { |
37 | private static final int MIN_CACHE_SIZE = 256; | ||
38 | |||
39 | private static final int MAX_CACHE_SIZE = TupleHashProvider.MAX_MODEL_SIZE; | ||
40 | |||
30 | public static final Cache INSTANCE = new Cache(); | 41 | public static final Cache INSTANCE = new Cache(); |
31 | 42 | ||
32 | private final ArrayList<Tuple1> tuple1Cache = new ArrayList<>(1024); | 43 | private final Object lock = new Object(); |
44 | |||
45 | // We don't want to synchronize the elements of the array, just the array reference itself, so an | ||
46 | // AtomicReferenceArray is not needed here and would degrade performance. | ||
47 | @SuppressWarnings("squid:S3077") | ||
48 | private volatile Tuple1[] tuple1Cache; | ||
33 | 49 | ||
34 | private Cache() { | 50 | private Cache() { |
51 | reset(); | ||
52 | } | ||
53 | |||
54 | public void reset() { | ||
55 | synchronized (lock) { | ||
56 | var newCache = new Tuple1[MIN_CACHE_SIZE]; | ||
57 | for (int i = 0; i < newCache.length; i++) { | ||
58 | newCache[i] = new Tuple1(i); | ||
59 | } | ||
60 | tuple1Cache = newCache; | ||
61 | } | ||
35 | } | 62 | } |
36 | 63 | ||
37 | public Tuple1 getOrCreate(int value) { | 64 | public Tuple1 getOrCreate(int value) { |
38 | if (value < 0) { | 65 | if (value < 0 || value >= MAX_CACHE_SIZE) { |
39 | // Mask tuple for QueryableModelStore, doesn't refer to a model node. | ||
40 | return new Tuple1(value); | 66 | return new Tuple1(value); |
41 | } | 67 | } |
42 | if (value < tuple1Cache.size()) { | 68 | var currentCache = tuple1Cache; |
43 | return tuple1Cache.get(value); | 69 | if (value < currentCache.length) { |
70 | return currentCache[value]; | ||
44 | } | 71 | } |
45 | Tuple1 newlyCreated = null; | 72 | synchronized (lock) { |
46 | tuple1Cache.ensureCapacity(value); | 73 | currentCache = tuple1Cache; |
47 | while (value >= tuple1Cache.size()) { | 74 | int currentSize = currentCache.length; |
48 | newlyCreated = new Tuple1(tuple1Cache.size()); | 75 | if (value < currentSize) { |
49 | tuple1Cache.add(newlyCreated); | 76 | return currentCache[value]; |
77 | } | ||
78 | // We don't have to worry about currentSize + (currentSize >> 1) overflowing, because MAX_CACHE_SIZE | ||
79 | // is only 30 bits. | ||
80 | int newSize = Math.min(Math.max(value + 1, currentSize + (currentSize >> 1)), MAX_CACHE_SIZE); | ||
81 | var newCache = Arrays.copyOf(currentCache, newSize); | ||
82 | for (int i = currentSize; i < newSize; i++) { | ||
83 | newCache[i] = new Tuple1(i); | ||
84 | } | ||
85 | tuple1Cache = newCache; | ||
86 | return newCache[value]; | ||
50 | } | 87 | } |
51 | return newlyCreated; | ||
52 | } | 88 | } |
53 | } | 89 | } |
54 | } | 90 | } |