【问题标题】:Terminate a benchmark early because of long score calculation由于长分数计算提前终止基准
【发布时间】:2017-03-01 20:05:57
【问题描述】:

我想要达到的目标

目前,我在我的 OptaPlanner 项目中运行大量输入,并且在当前实施的约束条件下,它们甚至需要很长时间才能计算初始分数。因此,给定的求解器会破坏整个基准,因为它会卡住并且无法终止。作为分数计算类型,我使用 Drools。

我正在尝试提前终止在一定时间后仍未通过初始分数计算的求解器(不显示“求解开始”)。因此,在单个基准测试中,我想运行多个不同的输入,并且对于每个输入,我都希望有一个给定的计时器,如果该计时器在初始分数计算完成之前到期,我希望求解器立即终止。一个理想的选择是获得分数计算完成的百分比。

我之所以不只是着手进行优化,是因为我希望有一个基线进行比较,并在优化进行时跟踪结果。因此,初始分数计算已通过多少百分比的信息对我来说至关重要。

我目前拥有/知道的内容

  1. 我正在使用的 OptaPlanner 版本是来自 GitHub 的版本,它已经打开了整个源代码(它不是网站上的官方版本,它是用 JAR 编译的,核心不可编辑)
  2. 为基准的每个求解器实现了计时器,在给定时间段后调用solver.terminateEarly() 方法。
  3. 每个求解器都在其唯一的线程上运行。所以关系求解器:线程是 1:1。我找出哪个求解器当前正在执行代码的方法是在 Map<Integer,Solver> solverMap 中查找,其中键是执行求解器的线程的 hashCode 值 -> Thread.currentThread().hashCode()。随着求解器开始和结束,此地图正在更新。这样我就可以从所有地方进行查找(optaplanner-examples、optaplanner-core、optaplanner-benchmark 项目和 Drools 规则(示例如下))
  4. 从 Drools 文档中找到 kcontext.getKieRuntime().halt(),用于立即终止规则执行。
  5. 实施的专门规则将在计划/影子实体的每次更改后到达 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


【解决方案1】:

如果可能的话,我会首先专注于让 DRL 更快,而不是这些 hack。所以这归结为找出哪些评分规则很慢。使用分数计算速度(在最后的 INFO 日志行中)通过注释掉分数规则并查看它们对分数计算速度的影响来确定这一点。

话虽如此,通常我建议查看unimprovedSecondsSpentLimit 或自定义Termination - 但这确实无济于事,因为在从头开始计算初始分数时不会检查这些内容:它们只是在每次移动之间检查(因此在每个fireAllRules() 之间,通常为 10k/秒)。

【讨论】:

    猜你喜欢
    • 2021-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多