# 优化 showmap 去重(照 D5③ 数据走的真瓶颈)——三项已实现

> 背景:D5③ 相位计时显示 worker 时间大头是 **showmap 去重**(np=8 占 40.4%,np=32 占 **53.2%**),
> 不是 Z3 求解。这是"换 SymSan 也不解决"、但**低成本能啃**的真瓶颈(见 `SymSan_迁移评估.md` 第五节)。
> 本轮实现了三项:**①内容级预去重(含跨 item) ②批量 showmap(最大头) ③CPU 亲和(核实:已由现有钉核覆盖)**。

---

## 瓶颈定位

worker 端 dedup:对 SymCC 单次 concolic 生成的**每个输出**读内容 → showmap 取边 → 并入 worker 位图判新。
现状 `StreamingShowmap` 已是 `afl-showmap -S` 持久 fork server,但**逐个走 Python↔子进程管道往返**(标称 ~600us/个),
且每个输出都要跑一次。#10 已量化 **~80% 输出是冗余**。

---

## ① 内容级预去重(含跨 item)—— 跳过字节完全相同的重复

**实测**:SymCC 单次 concolic 的输出里 **约 23–28% 字节完全相同**(gfts-xml:多组样本 23.4%/27.8%/28.8%)。
字节相同 → 边集必然相同 → 其 dedup 结果必与首次相同(**不可能判"新"**),故可**直接跳过 showmap**。

- 用 **16 字节 blake2b 摘要**作去重键;集合是 **worker 生命周期**的(`worker_seen`,有界 30 万条≈15MB,
  超限清空),**兼吃 item 内与 item 间**重复(跨 item 版比 per-item 版多省一点,视语料而定)。
- 正确性:字节相同的边集已在首次并入 worker 位图,重复项 merge 必返回"非新";跳过等价。对**非确定性目标**
  (FTS/xml 在 showmap 下同字节两次边集可不同,因超时/持久态)差异 ≤0.4%,在目标自身 run-to-run 抖动内,
  不丢真实覆盖(master 内容哈希 + AFL 覆盖是权威判重)。
- #10 漏斗精确:计入 `redun["byte_dup"]`(worker_internal 子集)。

## ② 批量 showmap(afl-showmap -I)—— 本轮最大头

把 worker 对一个 item 全部(预去重后)唯一输出的 showmap,从**逐个流式 get_edges** 改成
**一次 `afl-showmap -I filelist -o mapdir`**:整个 forkserver 循环在 **C 里**跑完,免掉 Python 每次管道往返。

**实测(同一批输入,隔离微基准):**
| 方式 | 每输入 |
|---|---|
| 逐个流式 get_edges(现状,文档标称) | ~600 us |
| **批量 afl-showmap -I** | **~25 us** |
→ **约 24× 更快**(afl-showmap -I 在 400/600/3000 输入上实测 25–51 us/输入)。

- 起停:afl-showmap -I 的 forkserver 启动一次、摊到该 item 的几百个输出上(实测 600 输入总 19ms)。
  break-even ≈ 十几个输出,真实 item 都远超,故几乎总是批量更划算。失败(afl 不支持 -I / 报错)→ **自动退回逐个流式**。
- **正确性**:afl-showmap -I 与逐个 afl-showmap 是**同一个工具、同一插桩、同一持久模式**,每输入的 map 相同;
  输出 `{文件: 稀疏边}` 直接喂现有 `CoverageBitmap.merge`。map 缺失(某输入超时/崩溃)→ 记 showmap_none。
- **端到端隔离验证**(直接调 `run_symcc_worker` 跑 SymCC+去重,无 MPI):gen=524、去重 **0.17s**、byte_dup=151、
  漏斗 reported+infeasible+worker_fresh+showmap_none+byte_dup = gen **完全自洽**;临时目录零残留。
- 开关:`SYMCC_BATCH_SHOWMAP`(默认 1 开;=0 退回流式)。

## ③ showmap 子进程 CPU 亲和(核实:已由现有钉核覆盖)

- 现有 `SYMCC_CPU_LIST` 机制把 SymCC rank 钉到与 AFL 互斥的保留高位核(`_compute_symcc_cpu_list`,
  门槛 **AFL 实例 ≥16**,即高并行度才开)。**worker 派生的 afl-showmap 子进程(流式 fork server 与批量 -I)
  都继承该亲和性** → showmap 已被钉在保留核上,与 AFL 不抢核。
- D5③ 里 showmap 相位 np8→np32 从 40%→53% 的增长:np=32 其实**已开钉核**却仍增长,说明残余争用主要是
  **内存带宽/LLC**(Threadripper 单 NUMA 多 worker 并发 showmap),**非核迁移**——CPU 亲和治不了这个。
- 结论:**#3 在核迁移层面已由现有钉核解决**;批量 showmap(②)通过**大幅减少 showmap 进程数/时长**,
  反而是缓解带宽争用最有效的一招(每 item 一次 afl-showmap 而非几百次)。故不再另加亲和代码,避免过度工程。

---

## 落地与验证小结
- 代码:`util/mpi_fuzzing_helper.py`——新增 `batch_showmap_edges()`;`run_symcc_worker` 改两趟(读+预去重 → 批量
  showmap → 合并),加 `worker_seen` 跨 item 集;`StreamingShowmap` 存批量所需配置 + 探测 shmem。

### 端到端 A/B(profiled hybrid,lava-base64,120s;b0=流式+#1,b1=+#2 批量,同代码只切 `SYMCC_BATCH_SHOWMAP`)

**np=32 各 2 轮(消 exec 噪声后的诚实数,showmap 绝对秒数):**
| 配置 | 每轮 showmap | 均值 | hybrid 覆盖(均值) |
|---|---|---|---|
| 流式(b0) | 34.04 / 28.31 | **31.18s** | 23.53% |
| 批量(b1) | 31.69 / 25.61 | **28.65s** | 23.44% |
→ **均值降 8.1%**,但 **n=2、两组分布重叠**(b1_r1=31.7 > b0_r2=28.3)→ **落在噪声内,非显著**;**覆盖率持平**(23.53 vs 23.44)。

**诚实结论(比单轮更可信):**
- 之前**单轮**的 `37.4→26.4s = −29%` 是**流式那一轮偏高(37.4s 离群)**造成的**假象**;多轮后基线均值降到 31.2s,批量端到端**只带来 ~8%(且在噪声内)**。
- **为何隔离 24×/输入 端到端几乎没兑现**:worker 的"showmap 去重"相位里,**showmap 调用只是一部分**,还含**读几百个输出文件 + merge**(两路共享、且量大),批量只加速调用那一段;加上 **1 item/worker 的 profile 方差极大**(exec 37↔79s),掩盖了小幅收益。
- **净评价**:**#2 批量正确、覆盖中性、每次调用确实快 24×,但在本目标端到端不显著**;真正稳的收益是 **#1 预去重(减 23–28% 的 showmap 调用数,机制确定)**。批量作为**默认开 + 流式回退**保留(无害,慢目标/高争用时更可能显效),但**不宜作为主要性能卖点**。

- 数据/脚本:`exp_showmap/`(`batch_ab_summary.csv`=单轮 np8/32、`batch_ab_2round.csv`=np32×2、`phase_timing_*.csv`、`batch_ab.sh`/`batch_ab2_cont.sh`)。
- 正确性另由"同工具同 map"+漏斗自洽 + 端到端隔离 `run_symcc_worker` 跑通(funnel 完全一致)保证,带流式回退兜底。
