Skip to content

Commit eda66c4

Browse files
committed
wip
1 parent 1ac995c commit eda66c4

3 files changed

Lines changed: 263 additions & 25 deletions

File tree

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/ArrayBindingSet.java

Lines changed: 98 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
*
99
* SPDX-License-Identifier: BSD-3-Clause
1010
*******************************************************************************/
11+
// Some portions generated by Codex
1112
package org.eclipse.rdf4j.query.algebra.evaluation;
1213

1314
import java.util.ArrayList;
1415
import java.util.Arrays;
16+
import java.util.BitSet;
1517
import java.util.Collections;
1618
import java.util.Iterator;
1719
import java.util.LinkedHashSet;
@@ -43,13 +45,22 @@ public class ArrayBindingSet extends AbstractBindingSet implements MutableBindin
4345

4446
private static final long serialVersionUID = -1L;
4547

48+
@InternalUseOnly
49+
public interface BindingNamesCache {
50+
Set<String> getBindingNames(long presentMask);
51+
52+
Set<String> getBindingNames(BitSet presentMask);
53+
}
54+
4655
private static final Logger logger = LoggerFactory.getLogger(ArrayBindingSet.class);
4756
private static final Value NULL_VALUE = Values
4857
.iri("urn:null:d57c56f3-41a9-468e-8dce-5706ebdef84c_e88d9e52-27cb-4056-a889-1ea353fa6f0c");
4958

59+
private final BindingNamesCache bindingNamesCache;
5060
private final String[] bindingNames;
5161

52-
// Creating a LinkedHashSet is expensive, so we should cache the binding names set
62+
// Creating a LinkedHashSet is expensive, so we should cache the binding names set (and ideally share it across
63+
// ArrayBindingSet instances created by the same QueryEvaluationContext).
5364
private Set<String> bindingNamesSetCache;
5465
private boolean empty;
5566

@@ -64,14 +75,27 @@ public class ArrayBindingSet extends AbstractBindingSet implements MutableBindin
6475
* @param names The binding names.
6576
*/
6677
public ArrayBindingSet(String... names) {
78+
this((BindingNamesCache) null, names);
79+
}
80+
81+
@InternalUseOnly
82+
public ArrayBindingSet(BindingNamesCache bindingNamesCache, String... names) {
83+
this.bindingNamesCache = bindingNamesCache;
6784
this.bindingNames = names;
6885
this.values = new Value[names.length];
6986
this.empty = true;
7087
}
7188

7289
public ArrayBindingSet(BindingSet toCopy, Set<String> names, String[] namesArray) {
90+
this(null, toCopy, names, namesArray);
91+
}
92+
93+
@InternalUseOnly
94+
public ArrayBindingSet(BindingNamesCache bindingNamesCache, BindingSet toCopy, Set<String> names,
95+
String[] namesArray) {
7396
assert !(toCopy instanceof ArrayBindingSet);
7497

98+
this.bindingNamesCache = bindingNamesCache;
7599
this.bindingNames = namesArray;
76100
this.values = new Value[this.bindingNames.length];
77101
for (int i = 0; i < this.bindingNames.length; i++) {
@@ -92,6 +116,12 @@ public ArrayBindingSet(BindingSet toCopy, Set<String> names, String[] namesArray
92116
}
93117

94118
public ArrayBindingSet(ArrayBindingSet toCopy, String... names) {
119+
this(null, toCopy, names);
120+
}
121+
122+
@InternalUseOnly
123+
public ArrayBindingSet(BindingNamesCache bindingNamesCache, ArrayBindingSet toCopy, String... names) {
124+
this.bindingNamesCache = bindingNamesCache;
95125
this.bindingNames = names;
96126

97127
this.values = Arrays.copyOf(toCopy.values, toCopy.values.length);
@@ -191,32 +221,80 @@ public Set<String> getBindingNames() {
191221
return Collections.emptySet();
192222
}
193223

194-
if (bindingNamesSetCache == null) {
195-
int size = size();
196-
if (size == 0) {
197-
this.bindingNamesSetCache = Collections.emptySet();
198-
} else if (size == 1) {
199-
for (int i = 0; i < this.bindingNames.length; i++) {
200-
if (values[i] != null) {
201-
this.bindingNamesSetCache = Collections.singleton(bindingNames[i]);
202-
break;
203-
}
204-
}
205-
assert this.bindingNamesSetCache != null;
224+
if (bindingNamesSetCache != null) {
225+
return bindingNamesSetCache;
226+
}
227+
228+
Set<String> bindingNamesSetCache;
229+
if (bindingNamesCache != null) {
230+
if (bindingNames.length <= Long.SIZE) {
231+
bindingNamesSetCache = bindingNamesCache.getBindingNames(toLongMask());
206232
} else {
207-
LinkedHashSet<String> bindingNamesSetCache = new LinkedHashSet<>(size * 2);
208-
for (int i = 0; i < this.bindingNames.length; i++) {
209-
if (values[i] != null) {
210-
bindingNamesSetCache.add(bindingNames[i]);
211-
}
212-
}
213-
this.bindingNamesSetCache = Collections.unmodifiableSet(bindingNamesSetCache);
233+
bindingNamesSetCache = bindingNamesCache.getBindingNames(toBitSet());
214234
}
235+
} else if (bindingNames.length <= Long.SIZE) {
236+
bindingNamesSetCache = toSetFromLongMask(toLongMask());
237+
} else {
238+
bindingNamesSetCache = toSetFromBitSet(toBitSet());
215239
}
216240

241+
this.bindingNamesSetCache = bindingNamesSetCache;
217242
return bindingNamesSetCache;
218243
}
219244

245+
private long toLongMask() {
246+
long mask = 0;
247+
for (int i = 0; i < values.length; i++) {
248+
if (values[i] != null) {
249+
mask |= (1L << i);
250+
}
251+
}
252+
return mask;
253+
}
254+
255+
private BitSet toBitSet() {
256+
BitSet bitSet = new BitSet(values.length);
257+
for (int i = 0; i < values.length; i++) {
258+
if (values[i] != null) {
259+
bitSet.set(i);
260+
}
261+
}
262+
return bitSet;
263+
}
264+
265+
private Set<String> toSetFromLongMask(long mask) {
266+
int size = Long.bitCount(mask);
267+
if (size == 0) {
268+
return Collections.emptySet();
269+
}
270+
if (size == 1) {
271+
return Collections.singleton(bindingNames[Long.numberOfTrailingZeros(mask)]);
272+
}
273+
274+
LinkedHashSet<String> set = new LinkedHashSet<>(size * 2);
275+
for (long bits = mask; bits != 0; bits &= (bits - 1)) {
276+
int index = Long.numberOfTrailingZeros(bits);
277+
set.add(bindingNames[index]);
278+
}
279+
return Collections.unmodifiableSet(set);
280+
}
281+
282+
private Set<String> toSetFromBitSet(BitSet bitSet) {
283+
int size = bitSet.cardinality();
284+
if (size == 0) {
285+
return Collections.emptySet();
286+
}
287+
if (size == 1) {
288+
return Collections.singleton(bindingNames[bitSet.nextSetBit(0)]);
289+
}
290+
291+
LinkedHashSet<String> set = new LinkedHashSet<>(size * 2);
292+
for (int index = bitSet.nextSetBit(0); index >= 0; index = bitSet.nextSetBit(index + 1)) {
293+
set.add(bindingNames[index]);
294+
}
295+
return Collections.unmodifiableSet(set);
296+
}
297+
220298
@Override
221299
public Value getValue(String bindingName) {
222300
if (isEmpty()) {

core/queryalgebra/evaluation/src/main/java/org/eclipse/rdf4j/query/algebra/evaluation/impl/ArrayBindingBasedQueryEvaluationContext.java

Lines changed: 143 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@
88
*
99
* SPDX-License-Identifier: BSD-3-Clause
1010
*******************************************************************************/
11+
// Some portions generated by Codex
1112
package org.eclipse.rdf4j.query.algebra.evaluation.impl;
1213

1314
import java.util.Arrays;
15+
import java.util.BitSet;
16+
import java.util.Collections;
1417
import java.util.Comparator;
1518
import java.util.HashMap;
1619
import java.util.HashSet;
1720
import java.util.LinkedHashMap;
21+
import java.util.LinkedHashSet;
1822
import java.util.List;
1923
import java.util.Set;
24+
import java.util.concurrent.ConcurrentHashMap;
2025
import java.util.function.BiConsumer;
2126
import java.util.function.Function;
27+
import java.util.function.LongFunction;
2228
import java.util.function.Predicate;
2329
import java.util.stream.Collectors;
2430

@@ -48,7 +54,8 @@
4854
import org.eclipse.rdf4j.query.algebra.helpers.AbstractSimpleQueryModelVisitor;
4955
import org.eclipse.rdf4j.query.impl.EmptyBindingSet;
5056

51-
public final class ArrayBindingBasedQueryEvaluationContext implements QueryEvaluationContext {
57+
public final class ArrayBindingBasedQueryEvaluationContext
58+
implements QueryEvaluationContext, ArrayBindingSet.BindingNamesCache {
5259

5360
public static final Predicate<BindingSet> HAS_BINDING_FALSE = (bs) -> false;
5461
public static final Function<BindingSet, Binding> GET_BINDING_NULL = (bs) -> null;
@@ -68,6 +75,12 @@ public final class ArrayBindingBasedQueryEvaluationContext implements QueryEvalu
6875
private final BiConsumer<Value, MutableBindingSet>[] addBinding;
6976
private final Comparator<Value> comparator;
7077

78+
private final LongFunction<Set<String>> bindingNamesFromLongMask = this::toBindingNamesSetFromLongMask;
79+
private final Function<BitSet, Set<String>> bindingNamesFromBitSet = this::toBindingNamesSetFromBitSet;
80+
81+
private final LongKeyCache<Set<String>> bindingNamesCacheLong = new LongKeyCache<>();
82+
private final ConcurrentHashMap<BitSet, Set<String>> bindingNamesCacheBitSet = new ConcurrentHashMap<>();
83+
7184
private final boolean initialized;
7285

7386
@InternalUseOnly
@@ -77,7 +90,7 @@ public ArrayBindingBasedQueryEvaluationContext(QueryEvaluationContext context, S
7790
this.context = context;
7891
this.allVariables = allVariables;
7992
this.allVariablesSet = Set.of(allVariables);
80-
this.defaultArrayBindingSet = new ArrayBindingSet(allVariables);
93+
this.defaultArrayBindingSet = new ArrayBindingSet(this, allVariables);
8194
this.comparator = comparator;
8295

8396
hasBinding = new Predicate[allVariables.length];
@@ -115,7 +128,7 @@ public Dataset getDataset() {
115128

116129
@Override
117130
public ArrayBindingSet createBindingSet() {
118-
return new ArrayBindingSet(allVariables);
131+
return new ArrayBindingSet(this, allVariables);
119132
}
120133

121134
@Override
@@ -329,14 +342,139 @@ public BiConsumer<Value, MutableBindingSet> addBinding(String variableName) {
329342
@Override
330343
public ArrayBindingSet createBindingSet(BindingSet bindings) {
331344
if (bindings instanceof ArrayBindingSet) {
332-
return new ArrayBindingSet((ArrayBindingSet) bindings, allVariables);
345+
return new ArrayBindingSet(this, (ArrayBindingSet) bindings, allVariables);
333346
} else if (bindings == EmptyBindingSet.getInstance()) {
334347
return createBindingSet();
335348
} else {
336-
return new ArrayBindingSet(bindings, allVariablesSet, allVariables);
349+
return new ArrayBindingSet(this, bindings, allVariablesSet, allVariables);
337350
}
338351
}
339352

353+
@Override
354+
public Set<String> getBindingNames(long presentMask) {
355+
return bindingNamesCacheLong.getOrCompute(presentMask, bindingNamesFromLongMask);
356+
}
357+
358+
@Override
359+
public Set<String> getBindingNames(BitSet presentMask) {
360+
return bindingNamesCacheBitSet.computeIfAbsent(presentMask, bindingNamesFromBitSet);
361+
}
362+
363+
private Set<String> toBindingNamesSetFromLongMask(long presentMask) {
364+
int size = Long.bitCount(presentMask);
365+
if (size == 0) {
366+
return Collections.emptySet();
367+
}
368+
if (size == 1) {
369+
return Collections.singleton(allVariables[Long.numberOfTrailingZeros(presentMask)]);
370+
}
371+
372+
LinkedHashSet<String> set = new LinkedHashSet<>(size * 2);
373+
for (long bits = presentMask; bits != 0; bits &= (bits - 1)) {
374+
int index = Long.numberOfTrailingZeros(bits);
375+
set.add(allVariables[index]);
376+
}
377+
return Collections.unmodifiableSet(set);
378+
}
379+
380+
private Set<String> toBindingNamesSetFromBitSet(BitSet presentMask) {
381+
int size = presentMask.cardinality();
382+
if (size == 0) {
383+
return Collections.emptySet();
384+
}
385+
if (size == 1) {
386+
return Collections.singleton(allVariables[presentMask.nextSetBit(0)]);
387+
}
388+
389+
LinkedHashSet<String> set = new LinkedHashSet<>(size * 2);
390+
for (int index = presentMask.nextSetBit(0); index >= 0; index = presentMask.nextSetBit(index + 1)) {
391+
set.add(allVariables[index]);
392+
}
393+
return Collections.unmodifiableSet(set);
394+
}
395+
396+
private static final class LongKeyCache<V> {
397+
398+
private static final int INITIAL_CAPACITY = 16;
399+
private static final float LOAD_FACTOR = 0.6f;
400+
401+
private long[] keys = new long[INITIAL_CAPACITY];
402+
private Object[] values = new Object[INITIAL_CAPACITY];
403+
private int size = 0;
404+
private int resizeThreshold = (int) (INITIAL_CAPACITY * LOAD_FACTOR);
405+
406+
public synchronized V getOrCompute(long key, LongFunction<? extends V> compute) {
407+
V existing = get(key);
408+
if (existing != null) {
409+
return existing;
410+
}
411+
412+
if (size >= resizeThreshold) {
413+
resize();
414+
}
415+
416+
V created = compute.apply(key);
417+
if (created == null) {
418+
throw new NullPointerException("value");
419+
}
420+
insert(key, created);
421+
return created;
422+
}
423+
424+
private V get(long key) {
425+
int index = hash(key) & (values.length - 1);
426+
while (true) {
427+
Object value = values[index];
428+
if (value == null) {
429+
return null;
430+
}
431+
if (keys[index] == key) {
432+
return (V) value;
433+
}
434+
index = (index + 1) & (values.length - 1);
435+
}
436+
}
437+
438+
private void insert(long key, V value) {
439+
int index = hash(key) & (values.length - 1);
440+
while (values[index] != null) {
441+
index = (index + 1) & (values.length - 1);
442+
}
443+
keys[index] = key;
444+
values[index] = value;
445+
size++;
446+
}
447+
448+
private void resize() {
449+
long[] oldKeys = keys;
450+
Object[] oldValues = values;
451+
452+
keys = new long[oldKeys.length * 2];
453+
values = new Object[oldValues.length * 2];
454+
size = 0;
455+
resizeThreshold = (int) (values.length * LOAD_FACTOR);
456+
457+
for (int i = 0; i < oldValues.length; i++) {
458+
Object oldValue = oldValues[i];
459+
if (oldValue == null) {
460+
continue;
461+
}
462+
insert(oldKeys[i], (V) oldValue);
463+
}
464+
}
465+
466+
private static int hash(long key) {
467+
long h = key;
468+
h ^= (h >>> 33);
469+
h *= 0xff51afd7ed558ccdL;
470+
h ^= (h >>> 33);
471+
h *= 0xc4ceb9fe1a85ec53L;
472+
h ^= (h >>> 33);
473+
return (int) h;
474+
}
475+
476+
}
477+
340478
public static String[] findAllVariablesUsedInQuery(QueryRoot node) {
341479
HashMap<String, String> varNames = new LinkedHashMap<>();
342480
AbstractSimpleQueryModelVisitor<QueryEvaluationException> queryModelVisitorBase = new AbstractSimpleQueryModelVisitor<>(

0 commit comments

Comments
 (0)