摘要

战场画面上方有一条行动条:一排小卡片,按顺序显示接下来轮到哪些部队行动。程序要认出每张卡是哪种生物,做法是把卡片和资料库里每种生物的标准图标逐一做“带遮罩的模板匹配”(在 4 个尺度、正反两个镜像方向上比较),按分数给出前三名候选,这一步叫排名(rank)。

测量一张开局截图时发现,热状态下战场读取近 20 秒,其中约 98.7% 花在 13 张卡的逐卡排名上。本实验准备了一个私有候选:把各卡的排名放进 2 个 Python 线程并行执行,结果按原卡序取回,算法、图标库、尺度、阈值都不变。实验设计了严格的冻结与计时协议,第一次正式测量因一个数据库辅助文件的修改时间变化,在发出第一个请求前就按规则停止;协议修正后再测,同一常驻进程的读取从 20.15 秒降到 12.38 秒(1.63 倍,减少 38.6%),完整输出与原方法逐字段一致。这只是一张图的开发诊断,没有接入默认流程。

要回答的问题

在不改变任何识别结果的前提下,把行动条逐卡排名并行化能省多少时间?瓶颈到底在哪一段?

数据与来源

  • 唯一的游戏输入:10 月 2 日一段录像第 420 秒附近的一张草地开局截图(实际解码时刻 420.0087 秒),沿用之前一次请求的配置(自动模式、GPU、指定玩家头像)。
  • 不使用其他测试图,也不导入参考答案。

方法

私有候选:只有“当前行动卡排名”这一步进入一个单次请求内的 2 线程池,卡片计划不超过 32 张;完成顺序不影响结果顺序;后面的卡先失败也不会覆盖前面卡的结果或异常。图标库冻结为只读,原生图片和模板都是只读视图。请求结束时等待、取消未开始的任务并关闭线程池,没有常驻线程或跨帧缓存。只接受已核查过的 OpenCV 4.10.0 构建,未知版本拒绝启用;不改 OpenCV 的全局线程设置。

冻结:运行前登记约 2,205 条文件元数据(源码、资料库、标准图标、OCR 模型缓存、Python 和依赖版本),任何变化都停止,不静默重新冻结或重跑。

计时协议:同一个新启动的常驻进程,依次执行“冷”请求一次(进程启动后的首次请求,不是硬件冷启动)、“热”请求一次;只有在两者都成功、战场读取成功、至少 2 张卡、热状态下排名占战场读取 ≥50%、冷/热的完整输出(去掉计时字段)严格一致时,才执行一次候选请求。总共最多 3 个请求,不重试、不额外预热、不换图。每一段(初始化、场景发现、路由 OCR、资源加载、各批 OCR、逐卡排名、战场识别、整体调用、进程 CPU)分别计时;各段有嵌套关系,不能相加。

输出比较:只删除三类计时字段,其余身份、数量、颜色、框、前三名顺序、所有分数、尝试记录、未知和错误都严格比较,不允许浮点容差,整数和浮点类型变化也算不一致。

设置

软件准备:新增 23 项测试,连同路由和行动者相关重点测试共 139 项通过;完整 Perception 回归 6,409 项通过、0 失败。测试用合成图标库和随机像素,验证真实的带遮罩 OpenCV 匹配、并列排序、镜像/尺度、只读源、任务乱序完成仍保持输出顺序等。没有构造真实模型。

结果

第一次测量:首请求前停止。 获得独占机器窗口后,冻结检查发现只有一个文件变了:资料库 SQLite 数据库的 -shm 辅助文件(共享内存索引文件,不含数据库内容),大小和 inode 不变,只有修改时间变了。按规则立即停止:真实请求 0 次,没有任何计时或比较。没有重试、放宽检查或删除文件,而是提出一个精确的协议修正:只排除这一个 -shm 文件的元数据变化,数据库本体和 -wal 文件(可能含已提交但未写回的事务)照常检查。

修正协议后的真实测量(3 次请求用满):

项目(秒) 冷 同进程热 同进程候选
整体调用 25.6589 20.1545 12.3779
战场识别 20.5798 19.7694 11.8812
进程 CPU 196.80 186.08 125.36
路由 OCR 4.8176 0.3001 0.4124
资料库资源加载 0.2054 ≈0 ≈0
13 卡逐卡排名累计 19.5750 19.5150 (并行,不走串行计时)

热状态下逐卡排名占战场读取 98.7127%,满足门槛。候选:计划 13 卡、完成 13、失败 0;两个线程的任务累计 21.84 秒,但外层实际等待只有 11.64 秒,线程池已关闭。冷↔热、热↔候选的完整输出都严格相等。

失败与负结果

  • 第一次正式测量因 -shm 文件修改时间变化而零请求停止,这个失败和原冻结计划都原样保留。修改时间是谁改的无法确定。
  • 并行任务的累计耗时(21.84 秒)略高于串行(19.51 秒),说明并行本身有开销;节省来自外层等待缩短。

局限

  • 只有一张图、三次请求,不能报 p95,不能推广到其他场景,也不证明 App 三秒目标或识别精度提升。
  • 候选后仍约 12.38 秒,远未达到实时要求。
  • “冷”只表示新常驻进程的首个请求,不是操作系统或 GPU 冷启动。
  • 候选没有公共导出,App 和命令行都不接入;默认路径、阈值、图标库、尺度都没变。

结论与下一步

瓶颈非常集中:行动条的逐卡模板匹配几乎占满了战场读取时间。两线程并行在不改变任何输出的情况下把这张图的读取缩短了约 39%。是否接入默认流程,需要先在其他已保存场景上做等价回放,并由消费端另行集成和验收;本次测量额度已用完,原证据和失败历史都保留。