# 性能：大文件为什么慢、怎么提速

> 结论：**不是 bug**，是"全文滑窗 × 逐窗推理"的线性成本；一台没有可用 GPU 的机器上，
> 3.5 MB 的压缩 JS 库约 76 秒，小文件则是 0.3–0.6 秒。

## 一、实测拆解（3.53 MB 的 echarts.js）

| 阶段 | 耗时 | 说明 |
|---|---|---|
| 预处理 | ~0.05 s | 引擎只读前 **1 MB**，清洗后文本上限 **20 万字符** |
| 特征提取（7 类正则 + 高熵） | 0.10 s | 不是瓶颈 |
| 分词 | 0.14 s | → **73,573 tokens** |
| 滑窗切分 | — | 512 token 窗 / **256 步长** → **287 个窗口** |
| 模型推理（CPU） | **3.56 窗口/秒** | 开发测试机 GTX 1060（算力 6.1）不支持 cu128 → 强制 CPU |
| **合计** | **≈ 76 s**（预估 81 s） | 其中约 99% 是推理 |

小文件只有 1–2 个窗口 → 0.3–0.6 秒，所以"别的文件不会"。

**为什么回落 CPU**：`torch 2.11.0+cu128` 的轮子最低要求算力 **sm_75**，而 GTX 10 系是 6.1。
若直接在 GPU 上跑，每个文件都会抛 `CUDA error: no kernel image is available`，而引擎的安全降级
会把它包装成「每份文件都转人工」——看起来像成功，其实零判别力。桥接进程因此在 import torch 之前
先做算力定档，低于 7.5 强制 CPU（`CUDA_VISIBLE_DEVICES=-1`）。

## 二、两套"滑窗"的关系（最容易混的点）

两者只共享一条关系式：**重叠 = 大小 − 步长**（等价：步长 = 大小 − 重叠）。

| | 模型侧滑窗（决定**快慢**） | 送审侧滑窗（决定**云端送什么**） |
|---|---|---|
| 单位 | token | 字符 |
| 大小 | **固定 512**（CodeBERT 上限），有效内容 510（预留 `[CLS]`/`[SEP]`） | 可调，默认 **3000** |
| 步长 | **可调**，默认 256，范围 1–511 | **隐式**（引擎算，面板不填） |
| 重叠 | **隐式** = 510 − 步长 = 254 | **可调**，默认 **500** |
| 窗口数 | `≈ (tokens − 510) / 步长 + 1` | `≈ (字符数 − 重叠) / (大小 − 重叠)` |
| 影响 | 推理耗时 | 云端**调用次数/费用**，不影响本地速度 |

模型的窗口切分（`calibguard/model.py::window_starts`）：

```python
W = max_length - 2                                  # 512 - 2 = 510
starts = range(0, n_tokens - W + 1, window_stride)  # 步长
# 尾部未覆盖时补一个贴文末的窗口
```

云端送审的切分（`calibguard/pipeline.py::_build_llm_payloads`）：

```python
size = cfg.llm_window_chars                        # 滑窗大小
step = max(1, size - cfg.llm_window_overlap)       # 步长 = 大小 − 重叠
```

> 注意：**步长 > 510 会出现空隙**（间隔大于窗口有效内容），因此"不重叠"应填 **510** 而不是 512。

用 echarts.js（73,573 tokens）验算：

| 模型步长 | 重叠 | 窗口数（公式 / 实测） | 耗时 |
|---|---|---|---|
| 256（默认） | 254 | `(73573−510)/256+1 = 287` / **287** | 76.6 s |
| 510 | 0 | `(73573−510)/510+1 = 145` / **145** | 39.0 s |

## 三、提速手段（都不改引擎代码）

| 手段 | 效果 | 代价 |
|---|---|---|
| 工具参数 `window_stride=510`，或插件配置 `windowStride: 510` | 窗口数约减半，耗时约减半 | 窗口几乎不重叠，跨边界的长逻辑可能被切开（默认 256 有 254 token 重叠，判断优先） |
| `llmEnabled: false` | 灰区不再联网复核 | 灰区按本地 0.5 切分 |
| 送审改 `truncate` + 较小 `llmTruncateChars` | 大文件最多 1 次云端调用（默认已是 truncate） | 送审内容更少 |
| 只审可疑的单文件/片段 | 直接线性减少窗口 | 需要先定位 |
| 换 ≥ 7.5 算力的 GPU | 吞吐量级提升 | 硬件成本 |

面板会在拖入大文件时按**实测吞吐**预估耗时（例如「预计 287 个滑窗，约 76 秒」）；
`calibguard_status` 也会返回 `performance.windowsPerSecond` 与当前滑窗参数。

> 引擎侧的 `MAX_INPUT_BYTES`(1 MB) / `MAX_TEXT_CHARS`(20 万字符) 是常量，本插件**不修改引擎**。

## 四、`maxFiles`（目录文件上限）的准确含义

审计目标可以是一个**目录**：引擎 `os.walk` 递归收集（按路径排序、跳过报告输出目录），
`maxFiles` 只限制这一步最多取多少个文件（默认 20，范围 1–200）：

| 你给的东西 | 受 `maxFiles` 限制？ |
|---|---|
| 一个目录 | ✅ 递归展开合计最多取 `maxFiles` 个 |
| 一个具体文件 | ❌ 全部审计 |
| 面板拖入/上传的文件（单次 ≤ 50） | ❌ 全部审计 |

超出时结果 `truncated=true`，工具摘要与面板都会提示「仅审计了前 N 个」。
默认只给 20 的理由：单个文件就可能几十秒，目录里混着 `node_modules`/素材包时不设上限会跑到失控。
