report 设计

设计目标

基准测试报告必须能从审计记录复现,并且可以安全地分享。report 是一个纯投影:从 JSONL 事件流到 Plot IR(ir_model),再从 Plot IR 到 JSON、SVG 和 HTML。它从不判定什么更快;它展示流中包含的内容,并且绝不让一次无效的测量看起来像有效的。

数学背景

投影

设流是一个事件序列。用 OO 表示 valid 为真且 batch_sink 为 "kept" 的观测,用 FF 表示状态为 invalid 或 infrastructure_failure 的验证所对应的键 (case,implementation,dataset)(\text{case}, \text{implementation}, \text{dataset}) 的集合。绘制的点为

P={ (dataseto,elapsedo,implementationo)  :  o∈O, (caseo,implementationo,dataseto)∉F },P = \{\, (\text{dataset}_o, \text{elapsed}_o, \text{implementation}_o) \;:\; o \in O,\ (\text{case}_o, \text{implementation}_o, \text{dataset}_o) \notin F \,\},

按流中的顺序排列。该投影对于不利于某个点的证据是单调的:增加一个失败的验证只会移除点,增加一个无效观测不会添加任何点。

坐标

一张图表占据 960×420960 \times 420 视框中的矩形 [76,880]×[102,354][76, 880] \times [102, 354]。若按首次出现顺序有 mm 个不同的 x 类别,则类别 ii 位于

X(i)=76+i 804m−1(m>1),X(0)=478(m=1).X(i) = 76 + i\,\frac{804}{m - 1} \quad (m > 1), \qquad X(0) = 478 \quad (m = 1).

设 vmin⁡v_{\min} 和 vmax⁡v_{\max} 为 y 的极值。y 的范围会加上留白,

[ℓ,h]={[vmin⁡−0.08 w, vmax⁡+0.08 w],w=vmax⁡−vmin⁡>0,[v−π, v+π],π=0.1max⁡(∣v∣,1),vmin⁡=vmax⁡=v,[\ell, h] = \begin{cases} [v_{\min} - 0.08\,w,\ v_{\max} + 0.08\,w], & w = v_{\max} - v_{\min} > 0, \\[2pt] [v - \pi,\ v + \pi],\quad \pi = 0.1\max(\lvert v\rvert, 1), & v_{\min} = v_{\max} = v, \end{cases}

空图表则为 [0,1][0, 1]。在每种情况下都有 h−ℓ>0h - \ell > 0,因此线性映射

Y(v)=354−(v−ℓ) 252h−ℓY(v) = 354 - (v - \ell)\,\frac{252}{h - \ell}

是良定义的,并且对每个绘制的值都有 ℓ<v<h\ell < v < h,这使每个点都严格位于绘图区域内部。热力图单元格使用不透明度 0.18+0.72 (v−ℓ)/(h−ℓ)0.18 + 0.72\,(v - \ell)/(h - \ell),因此它严格介于 0.180.18 和 0.900.90 之间:没有单元格是不可见的,也没有单元格是完全饱和的。

网格线绘制在 Y=102+63jY = 102 + 63j 处(j=0,…,4j = 0, \dots, 4),刻度值为 h−(h−ℓ) j/4h - (h - \ell)\,j/4,四舍五入到三位小数。若有 mm 个类别,则每隔 ⌈m/10⌉\lceil m/10\rceil 个类别以及在最后一个类别处绘制标签,因此最多显示十一个标签。

设计决策

纯投影,副作用在外

问题。 渲染器改动后需要重新生成报告,并在评审中进行比较。选择。 document_from_jsonl、plot_json、plot_svg 和 html 接收值并返回字符串;文件 IO、标准流和打开浏览器都位于 cli 中。理由。 同一份 JSONL 在每个目标上都给出相同的字节,测试不需要文件系统,HTML 也可以在更大的应用中生成。

失败会移除系列,并保持可见

问题。 一个返回错误结果的快速实现看起来会像是赢家。选择。 失败的验证会移除该实现在该用例和数据集上的点,并添加一行不匹配记录;最小化后的失败会添加一行带重放命令的反例。理由。 差分部分位于图表上方,因此读者能看到某个系列缺失的原因。

原始点加均值线

问题。 摘要掩盖分布,原始点云掩盖趋势。选择。 每个观测都带提示地绘制出来,对于线型图表,各类别的均值把类别连接起来。理由。 所绘各点的均值正是肉眼已经在估计的重心;稳健统计和决策属于 stats,可以作为更多图表添加。这条线是参考线,而不是报告为之担保的估计。

分类 x 轴

问题。 规模可能是整数、形状、布局或名称。选择。 PlotPoint.x 是字符串,坐标轴按首次出现的顺序均匀放置类别。理由。 数值坐标轴需要为每种规模定义一个类型。代价是间距不反映大小,而且 Pareto 视图只是分类散点图,而不是二维前沿。

转义顺序

文本转义按此顺序替换 &、<、>、" 和 '。& 必须排在最前:若在 < 之后再替换它,生成的 &lt; 就会变成 &amp;lt;。由于之后的每次替换引入的 & 都已经是某个实体的一部分,结果恰好能解码回输入一次。同一个函数既转义元素文本,也转义属性值。

自包含的输出

HTML 内嵌其 CSS 和内联 SVG,不加载任何字体、脚本或图像,并声明 color-scheme: light。它可以作为附件添加到 issue 中,或与 JSONL 一起归档,十年后渲染效果依然相同。

版本关卡

artifact_version 为 mmka_1 以外字符串的行会被拒绝,并报告其行号。没有该字段的行会被接受,因此手写的测试数据和较早的流仍然可读;非字符串的版本是错误。JSON 输出携带 schema_version mmks_1。

正确性与不变量

  • 确定性。 每个函数都是纯函数;相等的输入给出相等的字符串。
  • 排除。 没有点来自无效的、被丢弃的或被失败验证覆盖的观测(PP 的定义)。
  • 包含。 每个点都严格位于绘图区域内部,每个热力图不透明度都位于 (0.18,0.90)(0.18, 0.90) 中(推导见上文)。
  • 转义。 所有来自事件、标题、单位和系列名称的文本在进入 SVG 或 HTML 之前都会经过转义函数。
  • 复杂度。 解析与流的大小成线性关系,只是每个点都要与失败键列表比对,代价为 O(∣O∣⋅∣F∣)O(\lvert O\rvert \cdot \lvert F\rvert)。渲染含 NN 个点、mm 个类别和 SS 个系列的图表需要 O(N(m+S)+mSN)O(N(m + S) + mSN),因为每个单元格都会重新计算线的均值。

被否决的方案

  • JavaScript 图表库。 它需要脚本或 CDN,并会使输出依赖浏览器运行时。
  • 只渲染摘要。 会掩盖双峰性和离群值。
  • 数值 x 轴。 需要为每个用例定义规模类型;已推迟。
  • 把失败的实现视为零时间或无穷时间。 在计时坐标轴上,两者都是误导性的值。

边界

  • JSONL 投影只构建一张 Scaling 图表。当调用者在 Plot IR 中构造其他种类的图表时它们也会被渲染,但目前还没有任何代码从事件中派生它们。
  • 探索性阶段和验证性阶段的观测绘制在一起,x 值是数据集索引,而不是规模。
  • 目标由调用者传入;它不是从流中读取的。
  • 这里不计算统计量、决策或环境比较。
  • 样式、颜色和布局不属于兼容性承诺;mmks_1 JSON 结构才是。