Skip to content

Commit f10929e

Browse files
committed
GH-5447 improve performance of statement iterator
1 parent 8d8c498 commit f10929e

3 files changed

Lines changed: 399 additions & 13 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Eclipse RDF4J contributors.
3+
*
4+
* All rights reserved. This program and the accompanying materials
5+
* are made available under the terms of the Eclipse Distribution License v1.0
6+
* which accompanies this distribution, and is available at
7+
* http://www.eclipse.org/org/documents/edl-v10.php.
8+
*
9+
* SPDX-License-Identifier: BSD-3-Clause
10+
*******************************************************************************/
11+
12+
package org.eclipse.rdf4j.query.algebra.evaluation.impl.evaluationsteps;
13+
14+
import java.util.function.BiConsumer;
15+
16+
import org.eclipse.rdf4j.model.Statement;
17+
import org.eclipse.rdf4j.model.Value;
18+
import org.eclipse.rdf4j.query.MutableBindingSet;
19+
import org.eclipse.rdf4j.query.algebra.Var;
20+
import org.eclipse.rdf4j.query.algebra.evaluation.impl.QueryEvaluationContext;
21+
22+
class StatementConvertorWithoutBindingChecks {
23+
24+
private StatementConvertorWithoutBindingChecks() {
25+
}
26+
27+
public static BiConsumer<MutableBindingSet, Statement> s(QueryEvaluationContext context, Var s) {
28+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
29+
return (result, st) -> setS.accept(st.getSubject(), result);
30+
}
31+
32+
public static BiConsumer<MutableBindingSet, Statement> p(QueryEvaluationContext context, Var p) {
33+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
34+
return (result, st) -> setP.accept(st.getPredicate(), result);
35+
}
36+
37+
public static BiConsumer<MutableBindingSet, Statement> o(QueryEvaluationContext context, Var o) {
38+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
39+
return (result, st) -> setO.accept(st.getObject(), result);
40+
}
41+
42+
public static BiConsumer<MutableBindingSet, Statement> c(QueryEvaluationContext context, Var c) {
43+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
44+
return (result, st) -> setC.accept(st.getContext(), result);
45+
}
46+
47+
public static BiConsumer<MutableBindingSet, Statement> sp(QueryEvaluationContext context, Var s, Var p) {
48+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
49+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
50+
return (result, st) -> {
51+
setS.accept(st.getSubject(), result);
52+
setP.accept(st.getPredicate(), result);
53+
};
54+
}
55+
56+
public static BiConsumer<MutableBindingSet, Statement> so(QueryEvaluationContext context, Var s, Var o) {
57+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
58+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
59+
return (result, st) -> {
60+
setS.accept(st.getSubject(), result);
61+
setO.accept(st.getObject(), result);
62+
};
63+
}
64+
65+
public static BiConsumer<MutableBindingSet, Statement> sc(QueryEvaluationContext context, Var s, Var c) {
66+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
67+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
68+
return (result, st) -> {
69+
setS.accept(st.getSubject(), result);
70+
setC.accept(st.getContext(), result);
71+
};
72+
}
73+
74+
public static BiConsumer<MutableBindingSet, Statement> po(QueryEvaluationContext context, Var p, Var o) {
75+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
76+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
77+
return (result, st) -> {
78+
setP.accept(st.getPredicate(), result);
79+
setO.accept(st.getObject(), result);
80+
};
81+
}
82+
83+
public static BiConsumer<MutableBindingSet, Statement> pc(QueryEvaluationContext context, Var p, Var c) {
84+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
85+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
86+
return (result, st) -> {
87+
setP.accept(st.getPredicate(), result);
88+
setC.accept(st.getContext(), result);
89+
};
90+
}
91+
92+
public static BiConsumer<MutableBindingSet, Statement> oc(QueryEvaluationContext context, Var o, Var c) {
93+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
94+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
95+
return (result, st) -> {
96+
setO.accept(st.getObject(), result);
97+
setC.accept(st.getContext(), result);
98+
};
99+
}
100+
101+
public static BiConsumer<MutableBindingSet, Statement> spo(QueryEvaluationContext context, Var s, Var p, Var o) {
102+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
103+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
104+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
105+
return (result, st) -> {
106+
setS.accept(st.getSubject(), result);
107+
setP.accept(st.getPredicate(), result);
108+
setO.accept(st.getObject(), result);
109+
};
110+
}
111+
112+
public static BiConsumer<MutableBindingSet, Statement> spc(QueryEvaluationContext context, Var s, Var p, Var c) {
113+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
114+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
115+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
116+
return (result, st) -> {
117+
setS.accept(st.getSubject(), result);
118+
setP.accept(st.getPredicate(), result);
119+
setC.accept(st.getContext(), result);
120+
};
121+
}
122+
123+
public static BiConsumer<MutableBindingSet, Statement> soc(QueryEvaluationContext context, Var s, Var o, Var c) {
124+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
125+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
126+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
127+
return (result, st) -> {
128+
setS.accept(st.getSubject(), result);
129+
setO.accept(st.getObject(), result);
130+
setC.accept(st.getContext(), result);
131+
};
132+
}
133+
134+
public static BiConsumer<MutableBindingSet, Statement> poc(QueryEvaluationContext context, Var p, Var o, Var c) {
135+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
136+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
137+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
138+
return (result, st) -> {
139+
setP.accept(st.getPredicate(), result);
140+
setO.accept(st.getObject(), result);
141+
setC.accept(st.getContext(), result);
142+
};
143+
}
144+
145+
public static BiConsumer<MutableBindingSet, Statement> spoc(QueryEvaluationContext context, Var s, Var p, Var o,
146+
Var c) {
147+
BiConsumer<Value, MutableBindingSet> setS = context.addBinding(s.getName());
148+
BiConsumer<Value, MutableBindingSet> setP = context.addBinding(p.getName());
149+
BiConsumer<Value, MutableBindingSet> setO = context.addBinding(o.getName());
150+
BiConsumer<Value, MutableBindingSet> setC = context.addBinding(c.getName());
151+
return (result, st) -> {
152+
setS.accept(st.getSubject(), result);
153+
setP.accept(st.getPredicate(), result);
154+
setO.accept(st.getObject(), result);
155+
setC.accept(st.getContext(), result);
156+
};
157+
}
158+
}

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

Lines changed: 108 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public class StatementPatternQueryEvaluationStep implements QueryEvaluationStep
5353
private final TripleSource tripleSource;
5454
private final boolean emptyGraph;
5555
private final Function<Value, Resource[]> contextSup;
56-
private final BiConsumer<MutableBindingSet, Statement> converter;
56+
private BiConsumer<MutableBindingSet, Statement> converter;
57+
private BiConsumer<MutableBindingSet, Statement> convertStatementConverter;
5758
private final QueryEvaluationContext context;
5859
private final StatementOrder order;
5960

@@ -64,6 +65,11 @@ public class StatementPatternQueryEvaluationStep implements QueryEvaluationStep
6465
private final Function<BindingSet, Value> getPredicateVar;
6566
private final Function<BindingSet, Value> getObjectVar;
6667

68+
private final Var normalizedSubjectVar;
69+
private final Var normalizedPredicateVar;
70+
private final Var normalizedObjectVar;
71+
private final Var normalizedContextVar;
72+
6773
// We try to do as much work as possible in the constructor.
6874
// With the aim of making the evaluate method as cheap as possible.
6975
public StatementPatternQueryEvaluationStep(StatementPattern statementPattern, QueryEvaluationContext context,
@@ -137,9 +143,13 @@ public StatementPatternQueryEvaluationStep(StatementPattern statementPattern, Qu
137143
}
138144
}
139145

140-
converter = makeConverter(context, subjVar, predVar, objVar, conVar);
146+
normalizedSubjectVar = subjVar;
147+
normalizedPredicateVar = predVar;
148+
normalizedObjectVar = objVar;
149+
normalizedContextVar = conVar;
141150

142-
unboundTest = getUnboundTest(context, subjVar, predVar, objVar, conVar);
151+
unboundTest = getUnboundTest(context, normalizedSubjectVar, normalizedPredicateVar, normalizedObjectVar,
152+
normalizedContextVar);
143153

144154
}
145155

@@ -276,7 +286,7 @@ private JoinStatementWithBindingSetIterator getIteration(BindingSet bindings) {
276286
iteration = handleFilter(contexts, (Resource) subject, (IRI) predicate, object, iteration);
277287

278288
// Return an iterator that converts the statements to var bindings
279-
return new JoinStatementWithBindingSetIterator(iteration, converter, bindings, context);
289+
return new JoinStatementWithBindingSetIterator(iteration, getConverter(), bindings, context);
280290
} catch (Throwable t) {
281291
if (iteration != null) {
282292
iteration.close();
@@ -328,7 +338,7 @@ private ConvertStatementToBindingSetIterator getIteration() {
328338
iteration = handleFilter(contexts, (Resource) subject, (IRI) predicate, object, iteration);
329339

330340
// Return an iterator that converts the statements to var bindings
331-
return new ConvertStatementToBindingSetIterator(iteration, converter, context);
341+
return new ConvertStatementToBindingSetIterator(iteration, getConvertStatementConverter(), context);
332342
} catch (Throwable t) {
333343
if (iteration != null) {
334344
iteration.close();
@@ -340,6 +350,36 @@ private ConvertStatementToBindingSetIterator getIteration() {
340350
}
341351
}
342352

353+
private BiConsumer<MutableBindingSet, Statement> getConverter() {
354+
BiConsumer<MutableBindingSet, Statement> localConverter = converter;
355+
if (localConverter == null) {
356+
synchronized (this) {
357+
localConverter = converter;
358+
if (localConverter == null) {
359+
localConverter = makeConverter(context, normalizedSubjectVar, normalizedPredicateVar,
360+
normalizedObjectVar, normalizedContextVar);
361+
converter = localConverter;
362+
}
363+
}
364+
}
365+
return localConverter;
366+
}
367+
368+
private BiConsumer<MutableBindingSet, Statement> getConvertStatementConverter() {
369+
BiConsumer<MutableBindingSet, Statement> localConverter = convertStatementConverter;
370+
if (localConverter == null) {
371+
synchronized (this) {
372+
localConverter = convertStatementConverter;
373+
if (localConverter == null) {
374+
localConverter = makeConvertStatementConverter(context, normalizedSubjectVar,
375+
normalizedPredicateVar, normalizedObjectVar, normalizedContextVar);
376+
convertStatementConverter = localConverter;
377+
}
378+
}
379+
}
380+
return localConverter;
381+
}
382+
343383
private CloseableIteration<? extends Statement> handleFilter(Resource[] contexts,
344384
Resource subject, IRI predicate, Value object,
345385
CloseableIteration<? extends Statement> iteration) {
@@ -526,23 +566,23 @@ private static Resource[] fillContextsFromDatasSetGraphs(Set<IRI> graphs) {
526566
private static final class ConvertStatementToBindingSetIterator
527567
implements CloseableIteration<BindingSet> {
528568

529-
private final BiConsumer<MutableBindingSet, Statement> action;
569+
private final BiConsumer<MutableBindingSet, Statement> converter;
530570
private final QueryEvaluationContext context;
531571
private final CloseableIteration<? extends Statement> iteration;
532572
private boolean closed = false;
533573

534574
private ConvertStatementToBindingSetIterator(
535575
CloseableIteration<? extends Statement> iteration,
536-
BiConsumer<MutableBindingSet, Statement> action, QueryEvaluationContext context) {
576+
BiConsumer<MutableBindingSet, Statement> converter, QueryEvaluationContext context) {
537577
assert iteration != null;
538578
this.iteration = iteration;
539-
this.action = action;
579+
this.converter = converter;
540580
this.context = context;
541581
}
542582

543583
private BindingSet convert(Statement st) {
544584
MutableBindingSet made = context.createBindingSet();
545-
action.accept(made, st);
585+
converter.accept(made, st);
546586
return made;
547587
}
548588

@@ -573,27 +613,28 @@ public void close() throws QueryEvaluationException {
573613
private static final class JoinStatementWithBindingSetIterator
574614
implements CloseableIteration<BindingSet> {
575615

576-
private final BiConsumer<MutableBindingSet, Statement> action;
616+
private final BiConsumer<MutableBindingSet, Statement> converter;
577617
private final QueryEvaluationContext context;
578618
private final BindingSet bindings;
579619
private final CloseableIteration<? extends Statement> iteration;
580620
private boolean closed = false;
581621

582622
private JoinStatementWithBindingSetIterator(
583623
CloseableIteration<? extends Statement> iteration,
584-
BiConsumer<MutableBindingSet, Statement> action, BindingSet bindings, QueryEvaluationContext context) {
624+
BiConsumer<MutableBindingSet, Statement> converter, BindingSet bindings,
625+
QueryEvaluationContext context) {
585626
assert iteration != null;
586627
this.iteration = iteration;
587628
assert !bindings.isEmpty();
588-
this.action = action;
629+
this.converter = converter;
589630
this.context = context;
590631
this.bindings = bindings;
591632

592633
}
593634

594635
private BindingSet convert(Statement st) {
595636
MutableBindingSet made = context.createBindingSet(bindings);
596-
action.accept(made, st);
637+
converter.accept(made, st);
597638
return made;
598639
}
599640

@@ -683,6 +724,60 @@ private static BiConsumer<MutableBindingSet, Statement> makeConverter(QueryEvalu
683724

684725
}
685726

727+
private static BiConsumer<MutableBindingSet, Statement> makeConvertStatementConverter(
728+
QueryEvaluationContext context,
729+
Var s, Var p, Var o, Var c) {
730+
731+
if (s != null && !s.isConstant()) {
732+
if (p != null && !p.isConstant()) {
733+
if (o != null && !o.isConstant()) {
734+
if (c != null && !c.isConstant()) {
735+
return StatementConvertorWithoutBindingChecks.spoc(context, s, p, o, c);
736+
} else {
737+
return StatementConvertorWithoutBindingChecks.spo(context, s, p, o);
738+
}
739+
} else if (c != null && !c.isConstant()) {
740+
return StatementConvertorWithoutBindingChecks.spc(context, s, p, c);
741+
} else {
742+
return StatementConvertorWithoutBindingChecks.sp(context, s, p);
743+
}
744+
} else if (o != null && !o.isConstant()) {
745+
if (c != null && !c.isConstant()) {
746+
return StatementConvertorWithoutBindingChecks.soc(context, s, o, c);
747+
} else {
748+
return StatementConvertorWithoutBindingChecks.so(context, s, o);
749+
}
750+
} else if (c != null && !c.isConstant()) {
751+
return StatementConvertorWithoutBindingChecks.sc(context, s, c);
752+
} else {
753+
return StatementConvertorWithoutBindingChecks.s(context, s);
754+
}
755+
} else if (p != null && !p.isConstant()) {
756+
if (o != null && !o.isConstant()) {
757+
if (c != null && !c.isConstant()) {
758+
return StatementConvertorWithoutBindingChecks.poc(context, p, o, c);
759+
} else {
760+
return StatementConvertorWithoutBindingChecks.po(context, p, o);
761+
}
762+
} else if (c != null && !c.isConstant()) {
763+
return StatementConvertorWithoutBindingChecks.pc(context, p, c);
764+
} else {
765+
return StatementConvertorWithoutBindingChecks.p(context, p);
766+
}
767+
} else if (o != null && !o.isConstant()) {
768+
if (c != null && !c.isConstant()) {
769+
return StatementConvertorWithoutBindingChecks.oc(context, o, c);
770+
} else {
771+
return StatementConvertorWithoutBindingChecks.o(context, o);
772+
}
773+
} else if (c != null && !c.isConstant()) {
774+
return StatementConvertorWithoutBindingChecks.c(context, c);
775+
}
776+
777+
return (a, b) -> {
778+
};
779+
}
780+
686781
private static Predicate<Statement> andThen(Predicate<Statement> pred, Predicate<Statement> and) {
687782
if (pred == null) {
688783
return and;

0 commit comments

Comments
 (0)