Controlling Concurrency
ojAlgo v47.3 was released 2019-08-08. The main new features are:
- New, long overdue, functionality to help control multithreading
- Support for (some) Generalised Eigenvalue Problems
- A bunch of minor changes and improvements. To get a complete list of what’s changed check out the changelog.
Controlling Concurrency
ojAlgo is multithreaded – some operations are automatically divided to run in multiple threads. You don’t have to do anything for this to happen.
ojAlgo senses what hardware you’re running on and adapts to it. By default ojAlgo assumes it is allowed to max out the available hardware. This is something you may want to limit in a production environment.
There are (potentially) 3 thread pools / executor services used by ojAlgo:
- All low level math code use the same, ojAlgo-specific, org.ojalgo.concurrent.DaemonPoolExecutor instance. The instance always exists – nothing you can do about that. You can control the core number of threads in the pool, and if various algorithms actually multithread or not. And if they do multithread; then to how many parts to they divide the task. You only control this partially and indirectly. The example code below demonstrates how to do it. Most importantly this involves limiting the OjAlgoUtils.ENVIRONMENT.
- The IntegerSolver class (optimisation MIP solver) uses a private ForkJoinPool. This pool is not used for anything else and is only activated if you actually use the IntegerSolver. Its parallelism is derived from OjAlgoUtils.ENVIRONMENT.
- It is possible to use streams with various ojAlgo types, and if you do they can optionally be parallel. In that case they would use the ForkJoinPool.commonPool(). Please note that ojAlgo makes no direct/explicit use of this pool, but the default behaviour of any parallel stream is to use the common pool.
Example Code
ControllingConcurrency.javaimport static org.ojalgo.function.constant.PrimitiveMath.QUARTER;
import org.ojalgo.OjAlgoUtils;
import org.ojalgo.matrix.MatrixR064;
import org.ojalgo.netio.BasicLogger;
import org.ojalgo.random.Normal;
import org.ojalgo.random.Uniform;
import org.ojalgo.type.Stopwatch;
/**
* An example that, using matrix multiplication, demonstrates how to control ojAlgo's thread usage. To show
* the effects of the various alternatives the example meassures, and displays, execution times. What you
* should do is run this example yourself and have the Activity Monitor or Task Manager open so that you can
* see how many threads/cores are working on your machine. (If you do that you'll want to increase the matrix
* size.)
*
* @see https://www.ojalgo.org/2019/08/controlling-concurrency/
*/
public class ControllingConcurrency {
private static final int DIM = 1000;
public static void main(final String[] args) {
BasicLogger.debug();
BasicLogger.debug(ControllingConcurrency.class);
BasicLogger.debug(OjAlgoUtils.getTitle());
BasicLogger.debug(OjAlgoUtils.getDate());
BasicLogger.debug();
/*
* Create 2 random matrices, large enough
*/
MatrixR064 mtrxA = MatrixR064.FACTORY.makeFilled(DIM, DIM, Normal.standard());
MatrixR064 mtrxB = MatrixR064.FACTORY.makeFilled(DIM, DIM, Uniform.standard());
/*
* A runnable task that executes the multiplication
*/
Runnable task = () -> mtrxA.multiply(mtrxB);
task.run(); // Just some warm-up
BasicLogger.debug();
BasicLogger.debug("The 'environment' that ojAlgo detected and adapts to");
BasicLogger.debug(OjAlgoUtils.ENVIRONMENT);
/*
* Execute that task and meassure how long it takes to complete
*/
BasicLogger.debug();
BasicLogger.debug("Execution time (no configuration): {}", Stopwatch.meassure(task));
/*
* ojAlgo can be limited to only "see" part of the availale cores/threads.
*/
OjAlgoUtils.limitEnvironmentBy(QUARTER);
BasicLogger.debug();
BasicLogger.debug("The, now limited, environment that ojAlgo will adapt to");
BasicLogger.debug(OjAlgoUtils.ENVIRONMENT);
BasicLogger.debug("CPU units: {}", OjAlgoUtils.ENVIRONMENT.units);
BasicLogger.debug("CPU cores: {}", OjAlgoUtils.ENVIRONMENT.cores);
BasicLogger.debug("CPU threads: {}", OjAlgoUtils.ENVIRONMENT.threads);
/*
* Execute the multiplication task again
*/
BasicLogger.debug();
BasicLogger.debug("Execution time (limited environment): {}", Stopwatch.meassure(task));
/*
* Individual operations each have a concurrency threshold that can be modified. Doing this is
* normally not recommended/needed - it's for fine-tuning ojAlgo. There is a utility method that will
* push up all thresholds to a specified minimum value. This can be useful in some cases, but the
* alternative to "limit the environment" is preferable in most cases. Here we use that utility method
* and set the minimum threshold to the dimension of the matrices we created. That effectively means
* nothing will be forked off.
*/
OjAlgoUtils.pushUpConcurrencyThresholds(DIM);
/*
* Execute the multiplication task yet again
*/
BasicLogger.debug();
BasicLogger.debug("Execution time (limited environment and high thresholds): {}", Stopwatch.meassure(task));
}
}
Console Output
class ControllingConcurrency
ojAlgo
2019-08-07
The 'environment' that ojAlgo detected and adapts to
3GB/16threads HW=12GB/16threads,2xL3:8MB/8threads,8cores:32kB/2threads
Execution time (no configuration): 222.167222ms
The, now limited, environment that ojAlgo will adapt to
3GB/4threads HW=12GB/16threads,2xL3:8MB/8threads,8cores:32kB/2threads
CPU units: 1
CPU cores: 2
CPU threads: 4
Execution time (limited environment): 315.185725ms
Execution time (limited environment and high thresholds): 1384.959443ms
Recommendations
- Make sure ojAlgo correctly identifies what hardware you running. Print the OjAlgoUtils.ENVIRONMENT to check that.
- If you need/want to limit the environment ojAlgo will adapt to, then use one of the supplied methods in OjAlgoUtils.
- Modifying the operations’ concurrency thresholds (as in the above example) is a clumsy way to tamper with a library that tries to be fine-tuned. Try to avoid doing this!
- ← Previous
Generalised Eigenvalue Problems - Next →
AdoptOpenJDK HotSpot vs OpenJ9