【发布时间】:2017-03-01 20:05:57
【问题描述】:
我想要达到的目标
目前,我在我的 OptaPlanner 项目中运行大量输入,并且在当前实施的约束条件下,它们甚至需要很长时间才能计算初始分数。因此,给定的求解器会破坏整个基准,因为它会卡住并且无法终止。作为分数计算类型,我使用 Drools。
我正在尝试提前终止在一定时间后仍未通过初始分数计算的求解器(不显示“求解开始”)。因此,在单个基准测试中,我想运行多个不同的输入,并且对于每个输入,我都希望有一个给定的计时器,如果该计时器在初始分数计算完成之前到期,我希望求解器立即终止。一个理想的选择是获得分数计算完成的百分比。
我之所以不只是着手进行优化,是因为我希望有一个基线进行比较,并在优化进行时跟踪结果。因此,初始分数计算已通过多少百分比的信息对我来说至关重要。
我目前拥有/知道的内容
- 我正在使用的 OptaPlanner 版本是来自 GitHub 的版本,它已经打开了整个源代码(它不是网站上的官方版本,它是用 JAR 编译的,核心不可编辑)
- 为基准的每个求解器实现了计时器,在给定时间段后调用
solver.terminateEarly()方法。 - 每个求解器都在其唯一的线程上运行。所以关系求解器:线程是 1:1。我找出哪个求解器当前正在执行代码的方法是在
Map<Integer,Solver> solverMap中查找,其中键是执行求解器的线程的 hashCode 值 ->Thread.currentThread().hashCode()。随着求解器开始和结束,此地图正在更新。这样我就可以从所有地方进行查找(optaplanner-examples、optaplanner-core、optaplanner-benchmark 项目和 Drools 规则(示例如下)) - 从 Drools 文档中找到
kcontext.getKieRuntime().halt(),用于立即终止规则执行。 - 实施的专门规则将在计划/影子实体的每次更改后到达 then 部分,然后从 then 部分首先检查求解器是否提前终止(由相应的计时器),如果是则调用
kcontext.getKieRuntime().halt()。例如:
在下面的规则中,将在每次更改 ShiftAssignment 实例后到达 then 部分,如果将求解器设置为提前终止,则规则执行将停止。
salience 1 //so that it is triggered first
rule "ShiftAssignmentChange"
when
ShiftAssignment()
then
if(TerminateBenchmarkEarly.solverMap.get(Thread.currentThread().hashCode()).isTerminateEarly()){
kcontext.getKieRuntime().halt();//This command is used to terminate the fire loop early.
}
end
这些规则的意图是它们有salience 1 与默认选项 0 相对,因此它们将是第一个被执行的,并且规则执行将立即停止
6. 来自org.optaplanner.core.impl.score.director.drools.DroolsScoreDirector calculateScore 方法的kieSession.fireAllRules() 调用返回已执行的规则数。我可以使用这个度量作为初始分数达到多少的基准。随着优化的进行,预计这个数字会越来越高,所用的时间会越来越小。
我目前面临的问题
我遇到的问题是,即使再次实施,也需要花费大量时间来检查规则,或者在某些情况下由于 OutOfMemory 错误而崩溃。打开 Drools 的 Trace 选项,我可以看到它在一小部分时间内将事实插入到工作内存中,然后它不断输出TRACE BetaNode stagedInsertWasEmpty=false。问题出在org.optaplanner.core.impl.score.director.drools.DroolsScoreDirector calculateScore方法的kieSession.fireAllRules()调用,fireAllRules的代码来自Drools核心,这个代码被编译成JAR所以不能编辑。
结论
无论如何,我知道这在某种程度上是一种 hack,但正如我上面所说,我需要这些信息作为基线,以了解我当前的解决方案在哪里,并在优化进行时跟踪基准信息。 如果有不同(更智能)的方式可以实现这一点,我很乐意这样做。
基准测试结果
输入 1
- 实体数:12,870
- 变量计数:7,515
- 最大值计数:21
- 问题规模:22,068
- 加载 inputSolution 后(创建 Solver 之前)的内存使用量:平均 44,830,840 字节。
- 构建启发式后的平均分数计算速度 = 1965/秒
- 本地搜索后的平均分数计算速度 = 1165/秒
- Solver 完成后的平均分数计算速度 = 1177/秒
输入 2
- 实体数:17,559
- 变量计数:7,515
- 最大值计数:8
- 问题规模:21,474
- 加载 inputSolution 后(创建 Solver 之前)的内存使用量:平均 5,964,200 字节。
- 构建启发式后的平均分数计算速度 = 1048/秒
- 本地搜索后的平均分数计算速度 = 1075/秒
- Solver 完成后的平均分数计算速度 = 1075/秒
输入 3
- 实体数:34,311
- 变量计数:14,751
- 最大值计数:8
- 问题规模:43,358
- 加载 inputSolution 后(创建 Solver 之前)的内存使用量:平均 43,178,536 字节。
- 构建启发式后的平均分数计算速度 = 1134/秒
- 本地搜索后的平均分数计算速度 = 450/秒
- Solver 完成后的平均分数计算速度 = 452/秒
输入 4
- 实体数:175,590
- 变量计数:75,150
- 最大值计数:11
- 问题规模:240,390
- 加载 inputSolution 后(创建 Solver 之前)的内存使用量:平均 36,089,240 字节。
- 构建启发式后的平均分数计算速度 = 739/秒
- 本地搜索后的平均分数计算速度 = 115/秒
- Solver 完成后的平均分数计算速度 = 123/秒
输入 5
- 实体数:231,000
- 变量计数:91,800
- 最大值计数:31
- 问题规模:360,150
- 加载 inputSolution 后(创建 Solver 之前)的内存使用量:平均 136,651,744 字节。
- 构建启发式后的平均分数计算速度 = 142/秒
- 本地搜索后的平均分数计算速度 = 11/秒
- Solver 完成后的平均分数计算速度 = 26/秒
输入 6
- 实体数:770,000
- 变量计数:306,000 '
- 最大值计数: 51
- 问题规模:1,370,500
- 加载后的内存使用情况 inputSolution(在创建求解器之前):114,488,056 字节 平均。
- 构建后的平均分数计算速度 启发式 = 33/秒
- 本地搜索后的平均分数计算速度 = 1/秒
- Solver 完成后的平均分数计算速度 = 17/秒
注释掉 Drools 中的规则时,我得到下一个平均分 计算速度(输入 6):
- 构造后启发式 = 17800/秒
- 本地搜索后 = 22557/秒
- 求解器完成后 = 21690/秒
【问题讨论】:
-
有趣的问题。尽管初始分数计算需要更长的时间(因为它是从头开始计算的),但我从未见过它需要太长时间,即使对于大数据集也是如此。 你有多少个计划实体? 你的分数计算速度是每秒多少?
-
我编辑了问题并将基准测试结果发布到 6 个不同的输入。它们根据大小进行排序。从结果我们可以看出,随着问题变大,分数计算速度变慢。
-
目前我的域模型效率低下,因为有很多计划实体实例被创建但最终根本没有被使用。这样做的原因是因为我有一个 nullable = true 计划变量,因为我希望 OptaPlanner 选择哪些将被分配,哪些不会。
-
是的(请参阅文档“Sizing”),但不是那么多,以至于您的性能图会如此可怕 - 这显然是 (a) 瓶颈分数规则。
-
这个问题stackoverflow.com/questions/43282665/…讨论了我的规则中的瓶颈。一个简单的改变就产生了巨大的性能差异。
标签: drools optaplanner