上一篇介绍了OptaPlanner 7.32.0.Final版本中的SolverManager接口可以实现异步求解功能。本篇将继续介绍SolverManager的另一大特性 - 批量求解。
适用场景
在日常的规划系统中,求解一个问题,绝大多数情况下,容许运行的时间较有限,特别是在实时性较高的场景中,可让引擎运算时间不多。因此,这种情况下,会在启动了规划运算后,稍等片刻,即需要从求解程序中获取结果。但有些情况下,当我们遇到问题规模较大时,引擎无法在较短时间内找到相对最优解;甚至某些情况下,没有足够长的运行时间,可行解都可能无法找到。至于原因,可以参考我前面关于OptaPlanner入门文章中关于NPC, NP-Hard问题规模的说明。
因此,在一些规模大、时间要求不高的场景下,我们可以让引擎在空余时间自动运算。例如通过定时作业的方式,在非工作时间(例如晚间、节假日等)启动引擎对大多个规模的问题进行规划运算;第二天上班的时候,就可获得运算结果。在发布SolverManager之前,我们也完全可以针对不同的场景,设计相应的定时作业程序,让引擎运算自动运行。当然,这种方法与异步规划的问题一样,需要一定的系统设计经验才能把架构设计完美。而7.32.0.Final版本提供的SolverManager接口,则提供一种更简便的方法来实现些需求。
SolverManager批量规划特性
详细一下SolverManager接口,你应该会发现,与Solver对象的solve方法不同,使用SolverManager的sovle方法对一个问题进行求解时,除了把问题对象作为参数传入solve方法外,还需要传入一个problemID作为参数。其实对于这个参数很好理解,因为SolverManager可以同时对多个问题进行解,必须存在一种方法来识别不同的问题,规划完成后,调用方也需要通过指定的识别号,来获取相应的方案。SolverManager同时对多个问题进行求解时,对问题个数有何要求?它的求解行为是怎么样的呢?
SolverManager批量求解的行为
可同时求解多少个问题?
其实SolverManager不仅可以实现批量求解问题,而且可以实现同时对多 问题并行求解。通过设置SolverManager的parallelSolverCount属性,可以设置引擎在批量运算时,可以并行求解的问题数。即当SolverManger启动求解运算时,会将parallelSolverCount设定的数量的问题载入运算空间并行求解。通常parallelSolverCount值可以根据CPU、内存等计算机资源的情况,及问题的复杂度而推算。若无法判断此数量,也可以将parallelSolverCount设置为AUTO,引擎会根据具体情况,自动确定并行运算问题的数量,通常情况下,并行数是CPU核心数量的一半。
值得注意的是,此处的多个问题并行运算,与之前的求解过程多线程运算(Multithreaded incremental solving)是两个概念。多线程并行运算,指的是引擎在求解一个问题时,会将不同的可能解的子集分成多个线程同时计算(主要是计算约束分数和执行启发式算法)。而SolverManager的批量求解过程中,parallelSolverCount属性设定的,是引擎在面对多个问题时,同时求解的问题个数。大家可以设想,如果把Multithreaded incremental solving也启动起来,令引擎在对一个问题求解时可使用多个CPU核心,同时对多个问题并行求解。这种情况涉及的问题就没那么简单。因此,除非你对问题的复杂程度,CPU的核心和运算能力非常清晰,否则上述两个功能,还是设置为自动更好,引擎会根据计算资源运算时的工况,动态地自动确定并行求解的问题个数,及每个问题求解过程中应启动的线程个数。经历过单CPU多线程编程的朋友应该知道,多线程可以提高资源利用率,但有时候线程数量控制得不好,性能上却是起反效果的,最简单的是CPU的线程切换会消耗一定资源的。
批量求解的作用
在一些不太需要实时规划,规划求解不需要太频繁,运算需时较长的情况,批量求解就可以发挥较好作用。例如需要做一些季度或年度的大型供应链计划,因规划实休数量较大,问题空间可能非常巨大,需要花费相当长的时间才能得行相对最优解,甚至只能是可行解。可通过批量求解的方式,让引擎在空余时间(例如晚上、非工作日)进行运算,从而提高服务器资源的利用率。
基本用法
以下例子是OptaPlanner用户指南的例子,大家先作参考,目前还没有时间去研究SolverManager在示例程序中的代码,暂时也不知道官方示例中是否已经有SolverManager相关代码;若没有,稍后的时候我自己试用一下这些功能,再写一篇东西出来分享给大家。
public class TimeTableService {
private SolverManager<TimeTable, Long> solverManager;
// Returns immediately, call it for every dataset
public void solveBatch(Long timeTableId) {
solverManager.solve(timeTableId,
// Called once, when solving starts
this.findById,
// Called once, when solving ends
this.save);
}
public TimeTable findById(Long timeTableId) {...}
public void save(TimeTable timeTable) {...}
}
原创不易,如果觉得文章对你有帮助,欢迎点赞、评论。文章有疏漏之处,欢迎批评指正。
本系列文章在公众号不定时连载,请关注公众号(让APS成为可能)及时接收,二维码:
http://weixin.qq.com/r/WjtFXSvE2HanrW8_925I (二维码自动识别)
如需了解更多关于OptaPlanner的应用,请发电邮致:kentbill@gmail.com
或到讨论组发表你的意见:https://groups.google.com/forum/#!forum/optaplanner-cn
若有需要可添加本人微信(13631823503)或QQ(12977379)实时沟通,但因本人日常工作繁忙,通过微信,QQ等工具可能无法深入沟通,较复杂的问题,建议以邮件或讨论组方式提出。(讨论组属于google邮件列表,国内网络可能较难访问,需自行解决)