aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/store/src/main
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2022-09-27 03:24:29 +0200
committerLibravatar Kristóf Marussy <kristof@marussy.com>2022-10-03 20:06:52 +0200
commit974562c2c133734b251089cb8ea8fab39e138780 (patch)
tree48f96770564ebc5754cf1e1bb7f2e4e4a362472d /subprojects/store/src/main
parentrefactor: tuples in QueryableModel (diff)
downloadrefinery-974562c2c133734b251089cb8ea8fab39e138780.tar.gz
refinery-974562c2c133734b251089cb8ea8fab39e138780.tar.zst
refinery-974562c2c133734b251089cb8ea8fab39e138780.zip
fix: make Tuple1 cache thread safe
Diffstat (limited to 'subprojects/store/src/main')
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/model/TupleHashProvider.java6
-rw-r--r--subprojects/store/src/main/java/tools/refinery/store/tuple/Tuple1.java60
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 @@
1package tools.refinery.store.tuple; 1package tools.refinery.store.tuple;
2 2
3import java.util.ArrayList; 3import tools.refinery.store.model.TupleHashProvider;
4
5import java.util.Arrays;
4 6
5public record Tuple1(int value0) implements Tuple { 7public 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}