diff --git a/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc b/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc index d1f534f8a5..ec56c89040 100644 --- a/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc +++ b/docs/src/modules/ROOT/pages/constraints-and-score/overview.adoc @@ -33,10 +33,10 @@ image::constraints-and-score/overview/optimalWithIncompleteConstraints.png[align To implement a verbal business constraint, it needs to be formalized as a score constraint. Luckily, defining constraints in Timefold Solver is very flexible through the following score techniques: -* **Score signum (positive or negative)**: maximize or minimize a constraint type -* **Score weight**: put a cost/profit on a constraint type -* **Score level (hard, soft, ...)**: prioritize a group of constraint types -* *Pareto scoring* (rarely used) +Score signum (positive or negative):: Maximize or minimize a constraint type. +Score weight:: Put a cost/profit on a constraint type. +Score level (hard, soft, ...):: Prioritize a group of constraint types. +Pareto scoring (rarely used):: Optimize multiple objectives without weighting them against each other. Take the time to acquaint yourself with the first three techniques. Once you understand them, formalizing most business constraints becomes straightforward. @@ -411,18 +411,9 @@ A variant score type `BendableBigDecimalScore` uses `BigDecimal` values instead There are several ways to calculate the `Score` of a solution: -* **xref:constraints-and-score/score-calculation.adoc[Constraint Streams API]**: -Implement each constraint as a separate Constraint Stream. -Fast and scalable. -* **xref:constraints-and-score/score-calculation.adoc#incrementalScoreCalculation[Incremental score calculator]**: -Implement multiple low-level methods. -Fast and scalable. -Very difficult to implement and maintain. -Supports xref:constraints-and-score/understanding-the-score.adoc[score explanations] with extra effort. -* **xref:constraints-and-score/score-calculation.adoc#easyScoreCalculation[Easy score calculator]** (not recommended): -Implement all constraints together in a single method. -Does not scale. -Does not support xref:constraints-and-score/understanding-the-score.adoc[score explanations]. +xref:constraints-and-score/score-calculation.adoc[Constraint Streams API]:: Implement each constraint as a separate Constraint Stream. Fast and scalable. +xref:constraints-and-score/score-calculation.adoc#incrementalScoreCalculation[Incremental score calculator]:: Implement multiple low-level methods. Fast and scalable. Very difficult to implement and maintain. Supports xref:constraints-and-score/understanding-the-score.adoc[score explanations] with extra effort. +xref:constraints-and-score/score-calculation.adoc#easyScoreCalculation[Easy score calculator] (not recommended):: Implement all constraints together in a single method. Does not scale. Does not support xref:constraints-and-score/understanding-the-score.adoc[score explanations]. Every score calculation type can work with any Score definition (such as ``HardSoftScore`` or ``HardMediumSoftScore``). All score calculation types are object-oriented and can reuse existing Java code. diff --git a/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc b/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc index 962ab05b8c..5c11307e91 100644 --- a/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc +++ b/docs/src/modules/ROOT/pages/frequently-asked-questions.adoc @@ -80,41 +80,56 @@ image::running-timefold-solver/library/sizingHardware.png[align="center"] Understand these guidelines to decide the hardware for a Timefold Solver service: -* **RAM memory**: Provision plenty, but no need to provide more. -** The problem dataset, loaded before Timefold Solver is called, often consumes the most memory. It depends on the problem scale. -*** If this is a problem, review the domain class structure: remove classes or fields that Timefold Solver doesn't need during solving. -*** Timefold Solver usually has up to three solution instances: the internal working solution, the best solution and the old best solution (when it's being replaced). However, these are all a xref:domain-modeling/modeling-planning-problems.adoc#cloningASolution[planning clone] of each other, so many problem fact instances are shared between those solution instances. -** During solving, the memory is very volatile, because solving creates many short-lived objects. The Garbage Collector deletes these in bulk and therefore needs some heap space as a buffer. -** The maximum size of the JVM heap space can be in three states: -*** **Insufficient**: An `OutOfMemoryException` is thrown (often because the Garbage Collector is using more than 98% of the CPU time). -*** **Narrow**: The heap buffer for those short-lived instances is too small, therefore the Garbage Collector needs to run more than it would like to, which causes a performance loss. -**** Profiling shows that in the heap chart, the used heap space frequently touches the max heap space during solving. It also shows that the Garbage Collector has a significant CPU usage impact. -**** Adding more heap space increases the xref:constraints-and-score/performance.adoc#moveEvaluationSpeed[move evaluation speed]. -*** **Plenty**: There is enough heap space. The Garbage Collector is active, but its CPU usage is low. -**** Adding more heap space does _not_ increase performance. -**** Usually, this is around 300 to 500MB above the dataset size, _regardless of the problem scale_, +RAM memory:: Provision plenty, but no need to provide more. ++ +-- +* The problem dataset, loaded before Timefold Solver is called, often consumes the most memory. It depends on the problem scale. +** If this is a problem, review the domain class structure: remove classes or fields that Timefold Solver doesn't need during solving. +** Timefold Solver usually has up to three solution instances: the internal working solution, the best solution and the old best solution (when it's being replaced). However, these are all a xref:domain-modeling/modeling-planning-problems.adoc#cloningASolution[planning clone] of each other, so many problem fact instances are shared between those solution instances. +* During solving, the memory is very volatile, because solving creates many short-lived objects. The Garbage Collector deletes these in bulk and therefore needs some heap space as a buffer. +* The maximum size of the JVM heap space can be in three states: +** **Insufficient**: An `OutOfMemoryException` is thrown (often because the Garbage Collector is using more than 98% of the CPU time). +** **Narrow**: The heap buffer for those short-lived instances is too small, therefore the Garbage Collector needs to run more than it would like to, which causes a performance loss. +*** Profiling shows that in the heap chart, the used heap space frequently touches the max heap space during solving. It also shows that the Garbage Collector has a significant CPU usage impact. +*** Adding more heap space increases the xref:constraints-and-score/performance.adoc#moveEvaluationSpeed[move evaluation speed]. +** **Plenty**: There is enough heap space. The Garbage Collector is active, but its CPU usage is low. +*** Adding more heap space does _not_ increase performance. +*** Usually, this is around 300 to 500MB above the dataset size, _regardless of the problem scale_, except with xref:optimization-algorithms/move-selector-reference.adoc#nearbySelection[nearby selection] and caching move selector, neither of which are used by default. -* **CPU power**: More is better. -** Improving CPU speed directly increases the xref:constraints-and-score/performance.adoc#moveEvaluationSpeed[move evaluation speed]. -*** If the CPU power is twice as fast, it takes half the time to find the same result. However, this does not guarantee that it finds a better result in the same time, nor that it finds a similar result for a problem twice as big in the same time. -*** Increasing CPU power usually does not resolve scaling issues, because planning problems scale exponentially. Power tweaking the solver configuration has far better results for scaling issues than throwing hardware at it. -** During the `solve()` method, the CPU power will max out until it returns +-- + +CPU power:: More is better. ++ +-- +* Improving CPU speed directly increases the xref:constraints-and-score/performance.adoc#moveEvaluationSpeed[move evaluation speed]. +** If the CPU power is twice as fast, it takes half the time to find the same result. However, this does not guarantee that it finds a better result in the same time, nor that it finds a similar result for a problem twice as big in the same time. +** Increasing CPU power usually does not resolve scaling issues, because planning problems scale exponentially. Power tweaking the solver configuration has far better results for scaling issues than throwing hardware at it. +* During the `solve()` method, the CPU power will max out until it returns (except in xref:responding-to-change/real-time-planning.adoc#daemon[daemon mode] or if your xref:optimization-algorithms/overview.adoc#SolverEventListener[SolverEventListener] writes the best solution to disk or the network). -* **Number of CPU cores**: one CPU core per active Solver, plus at least one for the operating system. -** So in a multitenant application, which has one Solver per tenant, this means one CPU core per tenant, unless the number of solver threads is limited, as that limits the number of tenants being solved in parallel. -** With Partitioned Search, presume one CPU core per partition (per active tenant), unless the number of partition threads is limited. -*** To reduce the number of used cores, it can be better to reduce the partition threads (so solve some partitions sequentially) than to reduce the number of partitions. -** In use cases with many tenants (such as scheduling Software as a Service) or many partitions, it might not be affordable to provision that many CPUs. -*** Reduce the number of active Solvers at a time. For example: give each tenant only one minute of machine time and use a `ExecutorService` with a fixed thread pool to queue requests. -*** Distribute the Solver runs across the day (or night). This is especially an opportunity in SaaS that's used across the globe, due to timezones: UK and India can use the same CPU core when scheduling at night. -** The SolverManager will take care of the orchestration, especially in those underfunded environments in which solvers (and partitions) are forced to share CPU cores or wait in line. -* **I/O (network, disk, ...)**: Not used during solving. -** Timefold Solver is not a web server: a solver thread does not block (unlike a servlet thread), each one fully drains a CPU. -*** A web server can handle 24 active servlets threads with eight cores without performance loss, because most servlets threads are blocking on I/O. -*** However, 24 active solver threads with eight cores will cause each solver's xref:constraints-and-score/performance.adoc#moveEvaluationSpeed[move evaluation speed] to be three times slower, causing a big performance loss. -** Note that calling any I/O during solving, for example a remote service in your score calculation, causes a huge performance loss because it's called thousands of times per second, so it should complete in microseconds. So no good implementation does that. +-- + +Number of CPU cores:: One CPU core per active Solver, plus at least one for the operating system. ++ +-- +* So in a multitenant application, which has one Solver per tenant, this means one CPU core per tenant, unless the number of solver threads is limited, as that limits the number of tenants being solved in parallel. +* With Partitioned Search, presume one CPU core per partition (per active tenant), unless the number of partition threads is limited. +** To reduce the number of used cores, it can be better to reduce the partition threads (so solve some partitions sequentially) than to reduce the number of partitions. +* In use cases with many tenants (such as scheduling Software as a Service) or many partitions, it might not be affordable to provision that many CPUs. +** Reduce the number of active Solvers at a time. For example: give each tenant only one minute of machine time and use a `ExecutorService` with a fixed thread pool to queue requests. +** Distribute the Solver runs across the day (or night). This is especially an opportunity in SaaS that's used across the globe, due to timezones: UK and India can use the same CPU core when scheduling at night. +* The SolverManager will take care of the orchestration, especially in those underfunded environments in which solvers (and partitions) are forced to share CPU cores or wait in line. +-- + +I/O (network, disk, ...):: Not used during solving. ++ +-- +* Timefold Solver is not a web server: a solver thread does not block (unlike a servlet thread), each one fully drains a CPU. +** A web server can handle 24 active servlets threads with eight cores without performance loss, because most servlets threads are blocking on I/O. +** However, 24 active solver threads with eight cores will cause each solver's xref:constraints-and-score/performance.adoc#moveEvaluationSpeed[move evaluation speed] to be three times slower, causing a big performance loss. +* Note that calling any I/O during solving, for example a remote service in your score calculation, causes a huge performance loss because it's called thousands of times per second, so it should complete in microseconds. So no good implementation does that. +-- Keep these guidelines in mind when selecting and configuring the software. See https://timefold.ai/blog/[our blog archive] for the details of our experiments, which use our diverse set of examples. diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/benchmarking-and-tweaking.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/benchmarking-and-tweaking.adoc index 46650dd90e..3ff3f6f8b1 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/benchmarking-and-tweaking.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/benchmarking-and-tweaking.adoc @@ -1198,10 +1198,10 @@ image::running-timefold-solver/benchmarking-and-tweaking/benchmarkAggregator.png This is useful to: -* **Report on the impact of code changes**: Run the same benchmark configuration before and after the code changes, then aggregate a report. -* **Report on the impact of dependency upgrades**: Run the same benchmark configuration before and after upgrading the dependency, then aggregate a report. -* **Summarize a too verbose report**: Select only the interesting solver benchmarks from the existing report. This especially useful on template reports to make the graphs readable. -* **Partially rerun a benchmark**: Rerun part of an existing report (for example only the failed or invalid solvers), then recreate the original intended report with the new values. +Report on the impact of code changes:: Run the same benchmark configuration before and after the code changes, then aggregate a report. +Report on the impact of dependency upgrades:: Run the same benchmark configuration before and after upgrading the dependency, then aggregate a report. +Summarize a too verbose report:: Select only the interesting solver benchmarks from the existing report. This is especially useful on template reports to make the graphs readable. +Partially rerun a benchmark:: Rerun part of an existing report (for example only the failed or invalid solvers), then recreate the original intended report with the new values. Compose the aggregated report in the Benchmark aggregator UI: @@ -1308,10 +1308,10 @@ public File aggregateAndView() { The BenchmarkAggregator provides the following public methods: -* `aggregateBenchmarks(PlannerBenchmarkConfig)` - Aggregates all benchmark results found in the configured directory -* `aggregateSelectedBenchmarks(PlannerBenchmarkConfig, List)` - Aggregates benchmark results from specific subdirectories by name -* `getAvailableBenchmarkDirectories(PlannerBenchmarkConfig)` - Returns list of available benchmark subdirectories with valid results -* `aggregateAndShowReportInBrowser(PlannerBenchmarkConfig)` - Aggregates all benchmarks and opens the HTML report in the default browser +`aggregateBenchmarks(PlannerBenchmarkConfig)`:: Aggregates all benchmark results found in the configured directory. +`aggregateSelectedBenchmarks(PlannerBenchmarkConfig, List)`:: Aggregates benchmark results from specific subdirectories by name. +`getAvailableBenchmarkDirectories(PlannerBenchmarkConfig)`:: Returns list of available benchmark subdirectories with valid results. +`aggregateAndShowReportInBrowser(PlannerBenchmarkConfig)`:: Aggregates all benchmarks and opens the HTML report in the default browser. All aggregation methods return a `File` pointing to the generated HTML overview report that contains the aggregated benchmark results. diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/multithreaded-solving.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/multithreaded-solving.adoc index 01ff2584a3..617b5a1df0 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/multithreaded-solving.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/multithreaded-solving.adoc @@ -11,17 +11,16 @@ There are several ways of running the solver in parallel: -* *xref:#multithreadedIncrementalSolving[Multi-threaded incremental solving]*: -Solve 1 dataset with multiple threads without sacrificing xref:constraints-and-score/performance.adoc#incrementalScoreCalculationPerformance[incremental score calculation]. +xref:#multithreadedIncrementalSolving[Multi-threaded incremental solving]:: Solve 1 dataset with multiple threads without sacrificing xref:constraints-and-score/performance.adoc#incrementalScoreCalculationPerformance[incremental score calculation]. -* *xref:#partitionedSearch[Partitioned search]*: -Split 1 dataset in multiple parts and solve them independently. +xref:#partitionedSearch[Partitioned search]:: Split 1 dataset in multiple parts and solve them independently. -* *Multi bet solving*: solve 1 dataset with multiple, isolated solvers and take the best result. -** Not recommended: This is a marginal gain for a high cost of hardware resources. -** Use the xref:running-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] during development to determine the algorithm that is the most appropriate on average. -* *Multitenancy*: solve different datasets in parallel. -** The xref:running-timefold-solver/library/library-integration.adoc[`SolverManager`] can help with this. +Multi bet solving:: Solve 1 dataset with multiple, isolated solvers and take the best result. ++ +* Not recommended: This is a marginal gain for a high cost of hardware resources. +* Use the xref:running-timefold-solver/benchmarking-and-tweaking.adoc#benchmarker[Benchmarker] during development to determine the algorithm that is the most appropriate on average. + +Multitenancy:: Solve different datasets in parallel. The xref:running-timefold-solver/library/library-integration.adoc[`SolverManager`] can help with this. image::running-timefold-solver/multithreaded-solving/multiThreadingStrategies.png[align="center"] @@ -127,10 +126,9 @@ and the solver thread uses most of another CPU core. The following ``moveThreadCount``s are supported: -* `NONE` (default): Don't run any move threads. Use the single threaded code. -* ``AUTO``: Let Timefold Solver decide how many move threads to run in parallel. -On machines or containers with little or no CPUs, this falls back to the single threaded code. -* Static number: The number of move threads to run in parallel. +`NONE` (default):: Don't run any move threads. Use the single threaded code. +`AUTO`:: Let Timefold Solver decide how many move threads to run in parallel. On machines or containers with little or no CPUs, this falls back to the single threaded code. +Static number:: The number of move threads to run in parallel. It is counter-effective to set a `moveThreadCount` that is higher than the number of available CPU cores, @@ -340,11 +338,9 @@ image::running-timefold-solver/multithreaded-solving/partitionedSearchThreading. The following `runnablePartThreadLimit` options are supported: -* `UNLIMITED`: Allow Timefold Solver to occupy all CPU cores, do not avoid hogging. -Useful if a no hogging CPU policy is configured on the OS level. -* `AUTO` (default): Let Timefold Solver decide how many CPU cores to occupy. This formula is based on experience. -It does not hog all CPU cores on a multi-core machine. -* Static number: The number of CPU cores to consume. For example: +`UNLIMITED`:: Allow Timefold Solver to occupy all CPU cores, do not avoid hogging. Useful if a no hogging CPU policy is configured on the OS level. +`AUTO` (default):: Let Timefold Solver decide how many CPU cores to occupy. This formula is based on experience. It does not hog all CPU cores on a multi-core machine. +Static number:: The number of CPU cores to consume. For example: + [source,xml,options="nowrap"] ---- diff --git a/docs/src/modules/ROOT/pages/running-timefold-solver/solver-diagnostics.adoc b/docs/src/modules/ROOT/pages/running-timefold-solver/solver-diagnostics.adoc index 4296e33486..e570b96242 100644 --- a/docs/src/modules/ROOT/pages/running-timefold-solver/solver-diagnostics.adoc +++ b/docs/src/modules/ROOT/pages/running-timefold-solver/solver-diagnostics.adoc @@ -264,7 +264,7 @@ XML:: The best way to illuminate the black box that is a ``Solver``, is to play with the logging level: -* **error**: Log errors, except those that are thrown to the calling code as a ``RuntimeException``. +`error`:: Log errors, except those that are thrown to the calling code as a ``RuntimeException``. + [NOTE] ==== @@ -273,10 +273,10 @@ It does not log it as an error itself to avoid duplicate log messages. Except if the calling code explicitly catches and eats that ``RuntimeException``, a ``Thread``'s default `ExceptionHandler` will log it as an error anyway. Meanwhile, the code is disrupted from doing further harm or obfuscating the error. ==== -* **warn**: Log suspicious circumstances. -* **info**: Log every phase and the solver itself. See xref:optimization-algorithms/overview.adoc#scopeOverview[scope overview]. -* **debug**: Log every step of every phase. See xref:optimization-algorithms/overview.adoc#scopeOverview[scope overview]. -* **trace**: Log every move of every step of every phase. See xref:optimization-algorithms/overview.adoc#scopeOverview[scope overview]. +`warn`:: Log suspicious circumstances. +`info`:: Log every phase and the solver itself. See xref:optimization-algorithms/overview.adoc#scopeOverview[scope overview]. +`debug`:: Log every step of every phase. See xref:optimization-algorithms/overview.adoc#scopeOverview[scope overview]. +`trace`:: Log every move of every step of every phase. See xref:optimization-algorithms/overview.adoc#scopeOverview[scope overview]. [NOTE] ==== @@ -452,12 +452,10 @@ The following metrics are exposed: The names and format of the metrics vary depending on the registry. ==== -* `timefold.solver.errors.total`: the total number of errors that occurred while solving since the start -of the measuring. -* `timefold.solver.solve.duration.active-count`: the number of solvers currently solving. -* `timefold.solver.solve.duration.seconds-max`: run time of the -longest-running currently active solver. -* `timefold.solver.solve.duration.seconds-duration-sum`: the sum of each active solver's solve duration. For example, if there are two active solvers, one running for three minutes and the other for one minute, the total solve time is four minutes. +`timefold.solver.errors.total`:: The total number of errors that occurred while solving since the start of the measuring. +`timefold.solver.solve.duration.active-count`:: The number of solvers currently solving. +`timefold.solver.solve.duration.seconds-max`:: Run time of the longest-running currently active solver. +`timefold.solver.solve.duration.seconds-duration-sum`:: The sum of each active solver's solve duration. For example, if there are two active solvers, one running for three minutes and the other for one minute, the total solve time is four minutes. === Additional metrics