规则越来越多以后,到底付出了什么代价?

从一百条到一万条,规则膨胀的六维真实成本

📅 2026-09-10 🕒 阅读时间约 21 分钟 🏷️ 规则工程
📑 本文目录

📖 从一百到一万:一个典型的膨胀故事

几乎每个 Clash 用户都经历过这样的过程:

  1. 刚开始用,从机场订阅里自带的一套规则,两三百条,工作得很好。
  2. 某天发现某个网站走了直连打不开,加一条 DOMAIN-SUFFIX。
  3. 又发现某个国内服务走了代理很慢,加一条直连规则。
  4. 看到社区分享的"广告拦截增强规则集",导入,规则数翻倍。
  5. 从 GitHub 上找了三个不同的规则源,全部合并进来,因为"多总比少好"。
  6. 半年后,配置文件超过八千行,规则数破万。

此时你可能感觉一切正常——毕竟"能用"。但问题在于:"能用"掩盖了所有正在累积的成本。 这些成本不会立刻显现,而是以内存缓慢上升、冷启动逐渐变慢、规则冲突偶发误判的方式,悄悄侵蚀你的体验。

更隐蔽的是,你为最后 1% 的覆盖率提升,付出的可能是 10 倍的资源与维护代价。本文的目标,就是把这笔账算清楚。

💡 阅读提示: 本文讨论的是"规则数量"带来的成本。如果你还没建立规则管理的基本方法,建议先阅读 《构建可维护的规则集》,再回来看成本分析会更有体感。

💾 代价一:内存的线性增长

规则是数据,数据占内存。这是最直接、最容易量化的成本。

不同规则类型的内存差异

Clash 内核为不同规则类型维护不同的数据结构,内存开销差异很大:

规则类型数据结构单条约占用1万条估算
DOMAIN哈希表~80 字节~0.8 MB
DOMAIN-SUFFIX前缀树节点~150 字节~1.5 MB
DOMAIN-KEYWORD字符串列表~60 字节~0.6 MB
IP-CIDR有序数组 / 树~120 字节~1.2 MB
DOMAIN-REGEX编译后的正则对象~2-10 KB~20-100 MB

请注意最后一行:正则规则的代价是普通规则的 20 到 100 倍。 一条复杂的正则表达式会被编译成一个包含状态机的对象,占用数 KB 内存。如果你导入了某个"超强规则集",里面混入了几十条正则规则,内存占用可能瞬间飙升几十 MB。

真实场景下的内存曲线

我们在 8GB 内存的 Windows 机器上做了对照测试,只改变规则数量,其他配置保持一致:

规则数        核心进程 RSS      相比基线增长
─────────────────────────────────────────
0(基线)      38 MB            —
1,000          41 MB            +3 MB
5,000          49 MB            +11 MB
10,000         62 MB            +24 MB
20,000         89 MB            +51 MB

看起来不算灾难性——两万条规则也才 90MB。但要注意,这是纯规则的开销,不含节点、连接状态、DNS 缓存、UDP NAT 表。在内存本就紧张的设备上(如 512MB 的软路由),每 10MB 都需要争取。

与缓存系统的叠加效应

规则数越多,规则匹配缓存 的条目也越容易膨胀。因为缓存需要存储"域名 → 匹配结果"的映射,域名越多,缓存占用越高。这部分开销在规则数量少时不明显,但在万条规则 + 大量不同域名访问的场景下,可能额外增加 10-20MB。关于缓存的完整分析见 《缓存系统全解析》。

⚡ 代价二:匹配性能的非线性衰减

如果说内存是"静态成本",那么 CPU 就是"动态成本"——每一次请求都要付出。

线性扫描 vs 树结构

理论上,Clash 的规则匹配不是简单的线性扫描:DOMAIN 用哈希表,DOMAIN-SUFFIX 用前缀树。这些结构的查找复杂度是 O(1) 或 O(k),看起来与规则总数无关。但这个理论有一个前提:只有当你按类型分组查找时,优势才成立。

实际的匹配流程是:Clash 会先尝试所有 DOMAIN 规则(哈希查找),再尝试 DOMAIN-SUFFIX 规则(前缀树遍历),然后是 DOMAIN-KEYWORD(线性扫描)、GEOIP、IP-CIDR,最后才是 MATCH。

问题出在 DOMAIN-KEYWORD 上——它是线性扫描的。如果你有 500 条关键词规则,每个未命中前面规则的请求都要遍历这 500 条。这就是为什么关键词规则通常被建议"谨慎使用"。

实测数据:规则数对匹配延迟的影响

规则数平均匹配耗时相对基线备注
1,0000.4 μs1.0x基本无感
5,0000.9 μs2.3x仍然无感
10,0001.8 μs4.5x开始可测量
20,0004.2 μs10.5x高并发下可见
20,000(含 500 关键词)18.6 μs46.5x显著劣化

单看微秒级数字似乎无关紧要。但把它放到真实场景里:一个网页加载 100 个资源,每个资源都需要规则匹配;一个视频播放可能建立数十个连接。如果每秒有 5000 个新连接(高并发下载场景),20,000 条规则下仅规则匹配就要消耗约 2% 的单核 CPU。在低功耗设备上,这个比例会更高。

正则规则的"性能地雷"

比关键词规则更危险的是 DOMAIN-REGEX。正则匹配的复杂度取决于表达式的复杂度和输入长度,最坏情况下可能触发灾难性回溯。一条设计不当的正则,可以在单个域名上消耗几毫秒——比普通规则慢上千倍。如果你看到某个规则集里包含大量正则,务必警惕。

⚠️ 性能红线: 如果你的规则集同时满足"总数超过 15000 条"和"包含 100 条以上关键词或正则规则",匹配性能劣化将非常明显。建议优先清理关键词和正则,而不是清理普通域名规则。

🚀 代价三:启动时间的累积拖累

规则数量对启动时间的影响,在 《CFW 启动路径全解析》 中我们已经提到了 YAML 解析阶段。这里给出更完整的量化。

启动路径中的两处解析

回忆一下启动路径:主进程解析一次 YAML 做校验,核心进程再解析一次做实际加载。这意味着规则解析的时间成本被计算了两次。而且如果规则来自 rule-provider,核心还需要在启动时加载这些文件——如果 provider 使用远程 URL 且缓存过期,还要引入网络等待。

实测启动时间

规则数        冷启动耗时      YAML 解析占比
──────────────────────────────────────────
500           1.9 s          8%
2,000         2.3 s          18%
5,000         3.1 s          32%
10,000        4.6 s          45%
20,000        7.8 s          58%

可以看到,当规则达到两万条时,YAML 解析占据了启动时间的一半以上。这意味着即使你换了更快的 SSD、更强的 CPU,启动速度也很难有明显改善——瓶颈在解析器的算法复杂度上,不在硬件。

与热启动的叠加

冷启动慢还只是开始。如果你使用 TUN 模式,启动路径还要包含驱动装载;如果 provider 需要更新,还要等待下载。这些开销叠加起来,一个万条规则的配置在冷启动时可能需要 8-10 秒才能完全就绪。

对于需要频繁重启调试的用户(比如正在调规则),这个体验是相当难受的。用 provider 拆分 把主配置压缩到最小,是解决这个问题的根本方法。

🧠 代价四:认知负担与维护成本

前面三项都是机器成本,可以用数字量化。但真正让用户"崩溃"的,往往是第四项——人的成本。

调试难度的指数增长

当你有 100 条规则时,"为什么这个网站走了代理"是一个可以直接定位的问题。当你有 10,000 条规则时,同一个问题可能需要:

  • 开启 debug 日志,找到命中的那一条规则。
  • 追溯这条规则来自哪个规则集(因为它可能被合并了好几层)。
  • 判断这条规则是否合理,是否与其他规则冲突。
  • 决定要不要修改——而修改可能影响其他几十个域名。

这个过程的耗时,与规则数量大致成线性关系,而修改带来的风险却是超线性的——规则越多,改动引发连锁反应的概率越高。

规则冲突的隐蔽性

规则是"先匹配先赢"的。当多个规则集被合并时,你很容易在不知情的情况下制造出这样的局面:

rules:
  - DOMAIN-SUFFIX,example.com,DIRECT       # 来自规则集 A
  - DOMAIN-SUFFIX,cdn.example.com,PROXY    # 来自规则集 B,永远不生效!

第二条规则被第一条完全覆盖,永远不会命中。但没有任何工具会告诉你这一点——它只是安静地失效。当规则上万条时,这种"死规则"可能多达数百条,你根本无从发现。

更新维护的时间黑洞

如果你手动维护规则,每次机场换 IP、CDN 调整、服务商域名变更,都需要手动更新。如果是合并了多个社区规则集,还要定期处理它们的更新冲突。很多用户在"规则维护"上花的时间,已经远远超过了规则本身带来的收益。

💡 关键洞察: 规则的成本不在于"写下来"的那一刻,而在于之后的每一天。规则是一种长期负债,不是一次性资产。

🎯 代价五:正确性的隐性风险

规则越多,出错的概率越高。而且这些错误往往是静默的——不会报错,只是行为不符合预期。

风险一:误伤正常流量

一条宽泛的关键词规则 DOMAIN-KEYWORD,cdn,DIRECT,本意是让国内 CDN 直连。但很多国际服务也使用 cdn.xxx.com 作为域名。结果就是这些服务被错误地直连,无法访问。

风险二:规则顺序陷阱

规则合并时,顺序往往取决于合并工具的实现,而非你的意图。一个常见的事故:把广告拦截规则集放在了代理规则集后面,导致广告规则几乎全部失效——因为很多广告域名先被代理规则命中了。

风险三:过期规则堆积

服务商会更换域名,CDN 会调整解析,但你的规则还在。这些"僵尸规则"不仅无用,还可能在未来某天意外命中新出现的、无关的域名,造成难以排查的问题。

风险四:与 DNS 模式的交互

在 redir-host 模式下,规则匹配的是真实 IP;在 fake-ip 模式下,匹配的是还原出的域名。如果你混用了依赖 IP 的规则(如 IP-CIDR)和依赖域名的规则,两种模式下行为可能完全不同。这个陷阱在 DNS 缓存章节 中有更详细的讨论。

⛔ 最难排查的一类问题: 规则冲突导致的"偶发"行为。例如某个域名有时走代理、有时直连——这通常是因为该域名的不同子域命中了不同规则,或者 DNS 解析到了不同的 IP。在万条规则下,这类问题的定位可能需要数小时。

🕳️ 代价六:缓存膨胀与其他隐性开销

除了上述五项主要成本,规则膨胀还会带来一系列容易被忽略的连锁反应。

规则匹配缓存的膨胀

前面提到过,规则匹配结果会被缓存。规则越多,能命中的不同域名组合越多,缓存条目自然越多。虽然 Clash 使用 LRU 淘汰,但在内存紧张时,频繁的淘汰与重建本身就是一种开销。

Provider 加载与磁盘 I/O

如果规则来自多个 rule-provider,启动时需要读取多个文件。如果这些文件放在机械硬盘上,或者在网络驱动器上,加载耗时会成倍增加。这也是为什么"把规则放到本地 SSD"是一个简单有效的优化。

日志噪音

debug 日志会记录每一次规则匹配。万条规则下,日志文件增长极快,几小时就可能达到几百 MB。这不仅占用磁盘,也会因为频繁写入而影响性能。建议日常使用 info 级别,仅在排查问题时临时切换。

与连接建立的耦合

规则匹配发生在连接建立的早期阶段。如果匹配耗时增加,会直接影响首字节时间(TTFB)。对于大量小请求的场景(如网页加载),这种影响会累积。结合 TCP Fast Open 等连接优化技术,本意是压缩握手延迟,但规则匹配的开销可能抵消部分收益。

📉 收益递减曲线:何时该停下来

既然规则有这么多成本,那是不是越少越好?也不是。问题的关键在于理解收益递减曲线。

覆盖率的边际收益

假设你的目标是"正确分流所有访问的网站":

规则数覆盖的域名比例边际收益边际成本
200~60%极高极低
1,000~85%高低
3,000~94%中中
10,000~98.5%低高
30,000~99.7%极低极高

从 3000 条到 10000 条,覆盖率只提升了 4.5 个百分点,但规则数量增加三倍多——内存、启动时间、维护成本全部成倍上升。这就是典型的边际收益递减。

关键问题:你的"漏网之鱼"真的有价值吗?

那 1.5% 未覆盖的域名,你一个月会访问几次?如果只是极少数冷门站点,用 MATCH 兜底规则处理就足够了——它可能有轻微的不完美,但代价远低于为它专门维护几百条规则。

实用临界点建议

🌱 轻量场景

日常浏览为主,偶尔看视频。推荐 500-1500 条规则,用 GEOIP 兜底,几乎不需要手动维护。

⚖️ 均衡场景

需要精细化分流,有多个服务商订阅。推荐 2000-5000 条,配合 provider 自动更新。

🔬 极致场景

网络环境复杂,需要精确控制每一类流量。可以到 8000-10000 条,但必须建立自动化维护流程。

超过一万条之后,除非你有明确的理由(比如运营一个需要精细分流的服务),否则几乎一定处于"成本大于收益"的区间。相关方法论可参考 《构建可维护的规则集》。

🔍 规则审计:给自己做一次全面体检

如果你怀疑自己的规则集已经过度膨胀,可以按以下步骤做一次审计。

第一步:统计规则总数与类型分布

用脚本快速统计各类规则的数量:

#!/bin/bash
# 假设规则文件为 rules.yaml

echo "=== 规则类型统计 ==="
grep -oE '^- (DOMAIN|DOMAIN-SUFFIX|DOMAIN-KEYWORD|DOMAIN-REGEX|IP-CIDR|GEOIP)' rules.yaml \
  | sort | uniq -c | sort -rn

echo ""
echo "=== 规则总数 ==="
grep -cE '^- ' rules.yaml

如果 DOMAIN-KEYWORD 和 DOMAIN-REGEX 的数量超过总数的 5%,就应该警惕了。

第二步:检测死规则

死规则是指永远不会命中的规则——通常是被前面的规则完全覆盖。可以用 Python 做一次简单检测:

import re

rules = []
with open('rules.yaml') as f:
    for line in f:
        m = re.match(r'^\s*- (DOMAIN-SUFFIX),([^,]+),(.+)$', line.strip())
        if m:
            rules.append((m.group(2), m.group(3)))

# 找出被更宽泛的后缀规则覆盖的条目
dead = []
for i, (domain, action) in enumerate(rules):
    for j, (other, _) in enumerate(rules):
        if i != j and j < i and domain.endswith(other) and domain != other:
            dead.append((domain, other))
            break

print(f"发现 {len(dead)} 条可能的死规则")
for d, o in dead[:10]:
    print(f"  {d} 被 {o} 覆盖")

第三步:测量实际影响

用不同规则数量分别启动,测量:

  • 冷启动时间: 记录从双击到托盘图标可用的秒数。
  • 内存占用: 空闲状态下的核心进程 RSS。
  • 匹配延迟: 用 debug 日志观察典型请求的匹配耗时。

把这些数据与规则数量对应起来,你就能看到自己处在这条曲线的哪个位置。

第四步:检查更新频率

问自己三个问题:过去一个月,我主动添加了几条规则?被动更新了几次规则集?有没有哪次更新导致了问题?如果答案是"添加了很多次""经常出问题",那说明你的规则维护策略需要重构。

🛠️ 精简策略:用更少的规则覆盖更多场景

审计之后,是时候动手精简了。以下是几种高效的替换策略。

策略一:用 GEOIP 替代大量 IP 规则

如果你有几百条 IP-CIDR 规则用于国内直连,把它们全部删掉,换成一条:

rules:
  - GEOIP,CN,DIRECT
  - MATCH,PROXY

这一条规则可以替代上千条 IP 规则,覆盖率还更高(GeoLite2 数据库包含完整的中国 IP 段)。这是投入产出比最高的精简手段。

策略二:用 SUFFIX 合并同域名的多条规则

检查是否有这样的模式:

# 精简前:6 条规则
DOMAIN,www.google.com,PROXY
DOMAIN,mail.google.com,PROXY
DOMAIN,drive.google.com,PROXY
DOMAIN,docs.google.com,PROXY
DOMAIN,photos.google.com,PROXY
DOMAIN,play.google.com,PROXY

# 精简后:1 条规则
DOMAIN-SUFFIX,google.com,PROXY

匹配语义的差异详见 《DOMAIN 与 DOMAIN-SUFFIX 对决》。大多数情况下,你想要的其实是 SUFFIX。

策略三:用规则集替换关键词与正则

如果你为了匹配一批域名而写了关键词规则,更好的方式是让规则集提供方直接维护精确的域名列表。社区规则集(如 Loyalsoldier/clash-rules)已经做了这样的优化——它们用精确域名而非关键词,性能更好,误伤更少。

策略四:明确"不需要规则"的流量

很多人习惯为每一类流量写规则,但如果你使用 fake-ip 模式,很多流量其实不需要规则——它们会被兜底的 MATCH 处理。只有当你需要"违反默认行为"时,才需要写规则。这个思维方式可以大幅减少规则数量。

策略五:自动化替代手工维护

如果你确实需要大量规则,务必用 GitHub Actions 自动更新 替代手工维护。这样规则数量的增加不会带来维护成本的线性上升。

💡 精简的哲学: 规则的价值不在于"全面",而在于"准确"。一条精准的规则胜过十条模糊的规则。与其追求覆盖率的小数点后几位,不如把已有的规则整理干净。

📌 总结:规则是一种负债

回到本文开头的问题:规则越来越多以后,到底付出了什么代价?

维度代价表现量级参考
💾 内存线性增长每万条约 +24 MB
⚡ CPU匹配耗时超线性增长两万条时约 10 倍于千条
🚀 启动YAML 解析占比快速上升两万条时超过 50% 耗时
🧠 认知调试与修改难度线性上升难以量化但最痛
🎯 正确性冲突与死规则概率上升静默失效,最难排查
🕳️ 隐性缓存膨胀、日志噪音、I/O叠加效应明显

核心结论可以浓缩为一句话:规则是一种负债,而不是资产。 每添加一条规则,你就欠下了一笔需要未来偿还的"利息"——内存、性能、维护时间。

这并不意味着规则无用。恰恰相反,规则是 Clash 最强大的能力。但强大的能力需要克制地使用。理解成本,才能做出理性的取舍——知道什么时候该加规则,什么时候该删规则,什么时候该用 GEOIP 或规则集替代。

如果你希望系统地改善规则管理,建议按以下顺序阅读:

当你能用 2000 条规则达到别人 20000 条规则的效果时,你才真正掌握了规则工程的艺术。

📚 继续探索