Package com.singularsys.jep.misc.parallelparsing
Classes for parallel parsing of equations. To allow parallel parsing you need a separate parser for each thread, and a thread-safe variable table.
ConcurrentVariableTable
The ConcurrentVariableTable is a VariableTable backed by
a ConcurrentHashMap.
The ConcurrentVariableTable.addVariable(String)
are atomic operations ensuring that variables are created only once.
ParallelConfigurableParser
The ParallelConfigurableParser is a ConfigurableParser
that is safe to use in multiple threads. The only parings method
supported is ParallelConfigurableParser.parse(Reader)
which creates new instances of the Tokenizer and GrammarParser for
each invocation, so is safe to use in multiple threads.
The ParallelConfigurableParser.restart(java.io.Reader)
and ParallelConfigurableParser.continueParse() methods
are not implemented as they are not suitable for multiple
threaded used. If you need to parse multiple expressions in parallel
you can use the ParallelConfigurableParser.newMultipleEquationParser(java.io.Reader)
method to create a MultipleEquationParser in each thread.
The MultipleEquationParser.continueParsing()
method returns the result of parsing the next expression in the input.
To provide compatibility with the Jep class when using
the ParallelConfigurableParser, the MultipleEquationParsingJep
class provides implementations of the Jep.initMultiParse(java.io.Reader)
and Jep.continueParsing() methods
that work in a single threaded environment by using a single MultipleEquationParser.
The Jep class can be created as follows:
Jep jep = new Jep(
new ParallelConfigurableParser(new StandardConfigurableParser()),
new ConcurrentVariableTable());
A set of equations can be parsed in parallel as follows:
List<String> equations = new ArrayList<>(
List.of("x=1","y=2","z=x+y"));
// Service for executing threads
ExecutorService service = Executors.newFixedThreadPool(20);
// Used to wait for all threads to complete
CountDownLatch latch = new CountDownLatch(equations.size());
// The nodes created by the threads
ConcurrentLinkedQueue<Node> nodes = new ConcurrentLinkedQueue<>();
// Used to record failures
AtomicInteger failures = new AtomicInteger();
// loop over the equations, starting a new thread for each
for (String eqn : equations) {
service.execute(() -> {
try {
// Parse the equation
var node = jep.parse(eqn);
// Add the node to the list
nodes.add(node);
} catch (ParseException e) {
failures.incrementAndGet();
}
latch.countDown();
});
}
// Wait for all threads to complete
latch.await();
// Print the parsed nodes. Order is random
for (var node : nodes) {
jep.println(node);
}
Creating a new parser per thread
An alternative to using the ParallelConfigurableParser is to use
ConfigurableParser#ConfigurableParser(Jep, ConfigurableParser)
constructor, to build a new ConfigurableParser instance for each thread
that reuses the TokenMatchers, TokenFilters, and GrammarMatchers
from an existing ConfigurableParser instance.
Jep jep = new Jep(
new StandardConfigurableParser(),
new ConcurrentVariableTable());
List<String> equations = new ArrayList<>(List.of("x=1", "y=2", "z=x+y"));
// Service for executing threads
ExecutorService service = Executors.newFixedThreadPool(numberOfThreads);
// Used to wait for all threads to complete
CountDownLatch latch = new CountDownLatch(equations.size());
// The nodes created by the threads
ConcurrentLinkedQueue<Node> nodes = new ConcurrentLinkedQueue<>();
// Used to record failures
AtomicInteger failures = new AtomicInteger();
// loop over the equations, starting a new thread for each
for (String eqn : equations) {
service.execute(() -> {
try {
var myCp = new ConfigurableParser(jep, (ConfigurableParser) jep.getParser());
// Parse the equation
var node = myCp.parse(new StringReader(eqn));
// Add the node to the list
nodes.add(node);
} catch (ParseException e) {
failures.incrementAndGet();
}
latch.countDown();
});
}
// Wait for all threads to complete
latch.await();
// Print the parsed nodes. Order is random
for (var node : nodes) {
jep.println(node);
}
This is marginally faster.
Slower alternatives build a new StandardConfigurableParser
or StandardParser instance in each thread.
A detailed comparison of the different methods can be found at
ParallelParsingTest.
- See Also:
-
ClassesClassDescriptionA variable table backed by a ConcurrentHashMap that is safe for concurrent access.A parser which can be used to parse multiple expressions in sequence.A Jep which uses a
ParallelConfigurableParserproviding implementations of theJep.initMultiParse(java.io.Reader)andMultipleEquationParsingJep.continueParsing()methods.A version of the ConfigurableParser that can parse expressions in multiple threads.