/* * Copyright 2016 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.optaplanner.core.config.partitionedsearch; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ThreadFactory; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; import com.thoughtworks.xstream.annotations.XStreamImplicit; import org.optaplanner.core.api.solver.Solver; import org.optaplanner.core.config.constructionheuristic.ConstructionHeuristicPhaseConfig; import org.optaplanner.core.config.heuristic.policy.HeuristicConfigPolicy; import org.optaplanner.core.config.localsearch.LocalSearchPhaseConfig; import org.optaplanner.core.config.phase.PhaseConfig; import org.optaplanner.core.config.solver.EnvironmentMode; import org.optaplanner.core.config.util.ConfigUtils; import org.optaplanner.core.config.util.KeyAsElementMapConverter; import org.optaplanner.core.impl.partitionedsearch.DefaultPartitionedSearchPhase; import org.optaplanner.core.impl.partitionedsearch.PartitionedSearchPhase; import org.optaplanner.core.impl.partitionedsearch.partitioner.SolutionPartitioner; import org.optaplanner.core.impl.score.director.ScoreDirector; import org.optaplanner.core.impl.solver.ChildThreadType; import org.optaplanner.core.impl.solver.recaller.BestSolutionRecaller; import org.optaplanner.core.impl.solver.termination.Termination; import org.optaplanner.core.impl.solver.thread.DefaultSolverThreadFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @XStreamAlias("partitionedSearch") public class PartitionedSearchPhaseConfig extends PhaseConfig<PartitionedSearchPhaseConfig> { public static final String ACTIVE_THREAD_COUNT_AUTO = "AUTO"; public static final String ACTIVE_THREAD_COUNT_UNLIMITED = "UNLIMITED"; private static final Logger logger = LoggerFactory.getLogger(PartitionedSearchPhaseConfig.class); // Warning: all fields are null (and not defaulted) because they can be inherited // and also because the input config file should match the output config file protected Class<? extends SolutionPartitioner<?>> solutionPartitionerClass = null; @XStreamConverter(KeyAsElementMapConverter.class) protected Map<String, String> solutionPartitionerCustomProperties = null; protected Class<? extends ThreadFactory> threadFactoryClass = null; protected String runnablePartThreadLimit = null; @XStreamImplicit() protected List<PhaseConfig> phaseConfigList = null; // ************************************************************************ // Constructors and simple getters/setters // ************************************************************************ public Class<? extends SolutionPartitioner<?>> getSolutionPartitionerClass() { return solutionPartitionerClass; } public void setSolutionPartitionerClass(Class<? extends SolutionPartitioner<?>> solutionPartitionerClass) { this.solutionPartitionerClass = solutionPartitionerClass; } public Map<String, String> getSolutionPartitionerCustomProperties() { return solutionPartitionerCustomProperties; } public void setSolutionPartitionerCustomProperties(Map<String, String> solutionPartitionerCustomProperties) { this.solutionPartitionerCustomProperties = solutionPartitionerCustomProperties; } public Class<? extends ThreadFactory> getThreadFactoryClass() { return threadFactoryClass; } public void setThreadFactoryClass(Class<? extends ThreadFactory> threadFactoryClass) { this.threadFactoryClass = threadFactoryClass; } /** * Similar to a thread pool size, but instead of limiting the number of {@link Thread}s, * it limits the number of {@link java.lang.Thread.State#RUNNABLE runnable} {@link Thread}s to avoid consuming all * CPU resources (which would starve UI, Servlets and REST threads). * <p/> * The number of {@link Thread}s is always equal to the number of partitions returned by * {@link SolutionPartitioner#splitWorkingSolution(ScoreDirector, Integer)}, * because otherwise some partitions would never run (especially with {@link Solver#terminateEarly() asynchronous termination}). * If this limit (or {@link Runtime#availableProcessors()}) is lower than the number of partitions, * this results in a slower score calculation speed per partition {@link Solver}. * <p/> * Defaults to {@value #ACTIVE_THREAD_COUNT_AUTO} which consumes the majority * but not all of the CPU cores on multi-core machines, preventing other processes (including your IDE or SSH connection) * on the machine from hanging. * <p/> * Use {@value #ACTIVE_THREAD_COUNT_UNLIMITED} to give it all CPU cores. * This is useful if you're handling the CPU consumption on an OS level. * @return null, a number, {@value #ACTIVE_THREAD_COUNT_AUTO}, {@value #ACTIVE_THREAD_COUNT_UNLIMITED} * or a JavaScript calculation using {@value org.optaplanner.core.config.util.ConfigUtils#AVAILABLE_PROCESSOR_COUNT}. */ public String getRunnablePartThreadLimit() { return runnablePartThreadLimit; } public void setRunnablePartThreadLimit(String runnablePartThreadLimit) { this.runnablePartThreadLimit = runnablePartThreadLimit; } public List<PhaseConfig> getPhaseConfigList() { return phaseConfigList; } public void setPhaseConfigList(List<PhaseConfig> phaseConfigList) { this.phaseConfigList = phaseConfigList; } // ************************************************************************ // Builder methods // ************************************************************************ @Override public PartitionedSearchPhase buildPhase(int phaseIndex, HeuristicConfigPolicy solverConfigPolicy, BestSolutionRecaller bestSolutionRecaller, Termination solverTermination) { HeuristicConfigPolicy phaseConfigPolicy = solverConfigPolicy.createPhaseConfigPolicy(); DefaultPartitionedSearchPhase phase = new DefaultPartitionedSearchPhase( phaseIndex, solverConfigPolicy.getLogIndentation(), bestSolutionRecaller, buildPhaseTermination(phaseConfigPolicy, solverTermination)); phase.setSolutionPartitioner(buildSolutionPartitioner()); phase.setThreadFactory(buildThreadFactory()); phase.setRunnablePartThreadLimit(resolvedActiveThreadCount()); List<PhaseConfig> phaseConfigList_ = phaseConfigList; if (ConfigUtils.isEmptyCollection(phaseConfigList_)) { phaseConfigList_ = Arrays.asList( new ConstructionHeuristicPhaseConfig(), new LocalSearchPhaseConfig()); } phase.setPhaseConfigList(phaseConfigList_); phase.setConfigPolicy(phaseConfigPolicy.createChildThreadConfigPolicy(ChildThreadType.PART_THREAD)); EnvironmentMode environmentMode = phaseConfigPolicy.getEnvironmentMode(); if (environmentMode.isNonIntrusiveFullAsserted()) { phase.setAssertStepScoreFromScratch(true); } if (environmentMode.isIntrusiveFastAsserted()) { phase.setAssertExpectedStepScore(true); phase.setAssertShadowVariablesAreNotStaleAfterStep(true); } return phase; } private SolutionPartitioner buildSolutionPartitioner() { if (solutionPartitionerClass != null) { SolutionPartitioner<?> solutionPartitioner = ConfigUtils.newInstance(this, "solutionPartitionerClass", solutionPartitionerClass); ConfigUtils.applyCustomProperties(solutionPartitioner, "solutionPartitionerClass", solutionPartitionerCustomProperties); return solutionPartitioner; } else { if (solutionPartitionerCustomProperties != null) { throw new IllegalStateException("If there is no solutionPartitionerClass (" + solutionPartitionerClass + "), then there can be no solutionPartitionerCustomProperties (" + solutionPartitionerCustomProperties + ") either."); } // TODO throw new UnsupportedOperationException(); } } private ThreadFactory buildThreadFactory() { if (threadFactoryClass != null) { return ConfigUtils.newInstance(this, "threadFactoryClass", threadFactoryClass); } else { return new DefaultSolverThreadFactory("PartThread"); } } private Integer resolvedActiveThreadCount() { int availableProcessorCount = Runtime.getRuntime().availableProcessors(); Integer resolvedActiveThreadCount; if (runnablePartThreadLimit == null || runnablePartThreadLimit.equals(ACTIVE_THREAD_COUNT_AUTO)) { // Leave one for the Operating System and 1 for the solver thread, take the rest resolvedActiveThreadCount = availableProcessorCount <= 2 ? 1 : availableProcessorCount - 2; } else if (runnablePartThreadLimit.equals(ACTIVE_THREAD_COUNT_UNLIMITED)) { resolvedActiveThreadCount = null; } else { resolvedActiveThreadCount = ConfigUtils.resolveThreadPoolSizeScript( "runnablePartThreadLimit", runnablePartThreadLimit, ACTIVE_THREAD_COUNT_AUTO, ACTIVE_THREAD_COUNT_UNLIMITED); if (resolvedActiveThreadCount < 1) { throw new IllegalArgumentException("The runnablePartThreadLimit (" + runnablePartThreadLimit + ") resulted in a resolvedActiveThreadCount (" + resolvedActiveThreadCount + ") that is lower than 1."); } if (resolvedActiveThreadCount > availableProcessorCount) { logger.debug("The resolvedActiveThreadCount (" + resolvedActiveThreadCount + ") is higher than the availableProcessorCount (" + availableProcessorCount + "), so the JVM will round-robin the CPU instead."); } } return resolvedActiveThreadCount; } @Override public void inherit(PartitionedSearchPhaseConfig inheritedConfig) { super.inherit(inheritedConfig); solutionPartitionerClass = ConfigUtils.inheritOverwritableProperty(solutionPartitionerClass, inheritedConfig.getSolutionPartitionerClass()); solutionPartitionerCustomProperties = ConfigUtils.inheritMergeableMapProperty( solutionPartitionerCustomProperties, inheritedConfig.getSolutionPartitionerCustomProperties()); threadFactoryClass = ConfigUtils.inheritOverwritableProperty(threadFactoryClass, inheritedConfig.getThreadFactoryClass()); runnablePartThreadLimit = ConfigUtils.inheritOverwritableProperty(runnablePartThreadLimit, inheritedConfig.getRunnablePartThreadLimit()); phaseConfigList = ConfigUtils.inheritMergeableListConfig( phaseConfigList, inheritedConfig.getPhaseConfigList()); } }