Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

19 预算是内存不是时间

✅ 已验证 · 探针 probes/probe_stroke_budget.py · probe_throughput_tree.py

先量出来的数

单层空白 8192×12288 文档(probe_stroke_budget.py):

操作吞吐
paintLine(长约 300px)450–850 笔/秒,size 8/32/120/400 无差别
paintPath(8 点曲线)310–390 笔/秒
setForeGroundColor + paintLine,不 pump605 笔/秒
set_color()(带 pump(1))+ paintLine71 笔/秒——8.5 倍的税

成本与笔刷大小无关:大笔和小笔一样贵。换色换笔径不需要 pump()(连画 24 个色块不跑 event loop,读回来正好 24 种),Session.brush() 就是这条快路径。

真实图层树上的数

上面的数字骗了我一次。真实作业有 307 个带 clip 的节点,第一次跑:

 20000 笔  187 笔/秒
 60000 笔  121
100000 笔   99          ← 还在掉, Krita RSS 已经 18.6 GB

probe_throughput_tree.py 在同构的树上分开量:

画法笔/秒每 12000 笔的 RSS 增量
单层 · stroke() 逐段 paintLine140+2.8 GB
clip 树 · stroke() 逐段 paintLine130+3.9 GB
clip 树 · path() 一次 paintPath297+2.8 GB
同上 + Document.setBatchmode(True)291+2.3 GB

三个结论:

  1. 图层树只贵 7%(140 → 130),不是主因
  2. 调用次数是主因paintPath 一次调用比逐段 paintLine 快 2.3 倍
  3. 每次落笔调用约吃 233 KB 撤销栈setBatchmode 无效,Python API 里没有清撤销栈的接口(只有 Krita.setBatchmode / Document.setBatchmode,都不管撤销)

所以预算是内存

第一版规划了 241,162 笔。每笔平均 3.5 段,stroke() 逐段调 → 845,060 次调用 → 60 GB。本机 46 GB,会静默 OOM(Krita 被 OOM killer 干掉,一句话都不留)。

预算:46 GB − 基础 5 GB − 余量 ≈ 11 万次调用

三件事把调用数压下来:

做法效果
不收尖的笔走 path()(一次 paintPath调用数 = 笔数,而不是段数
落盘前 RDP 简化点列detail 从 3.4 段/笔降到约 1.2
strand 点列砍到 ≤ 9 点从 34.8 段/笔降到 8

现在:80,669 笔 → 99,755 次调用 → 撤销栈约 23 GB,217 秒,371 笔/秒。

trace_prepare.py 落盘时会自己算调用次数和内存估计,超过 11 万次打警告。调 ART_DETAIL_T(越大越少笔)来控。

什么时候需要逐段 paintLine

只有需要收尖的笔——pressure 是逐段给的,paintPath 全程一个压力。发丝、排线、线稿(如果收尖)走 stroke(pts, pressures),其余全部 path(pts)

离屏画布不是出路

Scratchpad 是文档之外的一块画布,本来指望它的落笔不进文档撤销栈。实测它上面 根本没有程序化落笔的 API,只有 fill*loadScratchpadImage——paintEngine / paintEvent / paintingActive 是 QWidget 自带的,别被名字骗了。省内存这条路不成立 (第 30 章)。

预算仍然只有一个办法:少调用

旧的坑

layers.md 时代记过 undoStackLimit=3 这个 kritarc 配置。它对 paint* 的撤销栈无效——第一版跑到 18.6 GB 就是在这个配置下。kritarc 里额外的内存相关键实测会直接搞坏绘画,保持最小配置。