从网络数据的重叠字节窗口出发,解释 GPU 如何用 2-byte key 和 4-byte tag 做病毒签名候选过滤,再由 CPU 验证并通过多 buffer 管线隐藏延迟。
学习目标
- 能解释为什么病毒检测要把“快速筛出候选”和“验证完整签名”拆成 GPU 与 CPU 两段
- 能修改 GPU Virus Filter Lab 的签名表规模、输入 buffer、tag 命中率、验证策略和 buffer 管线,观察候选回读如何改变吞吐
- 能回答:为什么一个命中 tag 不等于发现病毒,以及当签名跨 buffer 边界时怎样避免漏检
先问:为什么要先做一个很快的筛子
网络设备面对的不是一份整齐的文件,而是一条持续流入、大小不一、可能跨越多个对象的字节流。每一段数据都可能包含恶意模式;如果每次都让最慢的完整检查从头读到尾,吞吐很快就会被拖垮。
本章把工作拆成两层:先用大量轻量线程找出“看起来像”的位置,再把少量候选交给更擅长复杂判断的处理器。这样没有候选时可以快速通过,有候选时也仍然保留完整检查的准确性。
1. 把完整签名匹配拆成候选过滤和最终验证
↡把输入字节流与已知恶意模式逐段比较、找出候选位置并最终确认匹配的过程。
原始库以固定字符签名为第一步:GPU 负责规则短、访存规律的候选筛选,CPU 负责完整签名验证和对象归属。正则表达式、解压、文件类型判断和最终报告并不自动变成 GPU 工作;它们仍属于应用或 CPU 侧的可变逻辑。
GPU 与网络处理器有相似之处:都需要快速内存、用很多线程隐藏 DRAM 延迟,并持续搬运大批量数据。但相似不意味着所有阶段都适合并行;“候选过滤”才是本章刻意挑出的规则核心。
2. 选择并行方向:一个包内,还是多个包之间
↡在同一个数据包或文件内部,把相邻字节窗口分给多个线程同时处理。
↡把多个数据包或文件同时交给不同线程或工作组,每个工作单元处理自己的对象。
intrapacket 让一个长对象内部的相邻窗口并行;interpacket 则让多个对象同时前进。实际库把数据先填满 buffer,再由 GPU 忽略对象边界地扫描连续字节,最后由 CPU 使用对象描述把候选归回文件。这个设计避免 GPU 在线程中维护复杂的文件状态。
两种布局的共同要求是:线程必须能独立读固定数量的字节,输出也必须能写到确定位置。于是下一步不是把完整签名复制给每个线程,而是为每个窗口准备一个很小的索引和比较值。
3. 用 2-byte key 把签名数据库压成一次查表
↡从签名或输入中取出的连续两个字节,用作 64,000-entry GPU 表的索引。
对每个固定字符签名,数据库取一对连续字节作为 key,并把它前面的四个字节存成 tag。GPU 线程读取输入中的一个 2-byte window,用这个 key 索引表,再把表里的 tag 与输入窗口之前的四个字节比较。两个比较都匹配时,只说明这里值得 CPU 进一步检查。
↡跟随 key 存在表项中的四个前置字节;它让 GPU 用一次小比较快速排除大多数不相关窗口。
可以把线程的主循环压缩成下面的伪代码;关键不是语言,而是每个线程读取固定窗口、做一次索引和一次短比较:
for each adjacent pair at offset i:
key = input[i : i + 2]
tag = table[key]
if input[i - 4 : i] matches tag:
output[2 * i] = signatureId
这个表故意把“快”放在第一位:每个 entry 原始设计至多对应一个签名,避免 GPU 为每个窗口追逐复杂链表。代价是签名集合、key 唯一性和内存容量都成为明确的可扩展性边界。
4. 重叠窗口与结果缓冲区决定正确性成本
线程 A 读取 [i, i + 1],线程 B 读取 [i + 1, i + 2],这种重叠让每个连续 2-byte pair 都有机会成为 key。若签名的关键字节跨越输入 buffer 末尾,应用必须复制足够的前置数据到下一个 buffer;复制量至少要覆盖最长签名所需的上下文。
命中时 GPU 不写入整条签名,而是在输出 buffer 的相对偏移 2i 写入两字节 identifier。没有命中的位置保持空值,CPU 之后扫描这些稀疏结果,再根据对象描述把 offset 转成文件或数据包中的位置。
↡GPU 过滤器报告的候选不一定是真匹配;只有 CPU 验证完整签名后,才能确认或排除它。
动手走一遍:一个 buffer 如何完成过滤并回到对象结果
⚡分步1 / 4
先填满输入 buffer
CPU 先完成文件类型判断、解压等可变工作,把一个或多个对象拼入连续 buffer,并保留对象描述与跨边界所需的前置字节。
5. 用多 buffer 把两个处理器变成一条流水线
如果 CPU 填一块 buffer、等待 GPU、再读回结果,GPU 工作时 CPU 可能闲着,CPU 验证时 GPU 也可能闲着。轮转多个 buffer 后,CPU 可以填充下一块,GPU 扫描当前块,CPU 同时验证上一块;只有结果 buffer 的回读在原始实现中仍然是串行点。
第 1 / 4 步 · CPU 继续填充下一个数据 buffer,同时把当前 buffer 提交给 GPU
逐步观察 CPU 和 GPU 如何通过多 buffer 轮转隐藏等待。
轮流使用多块输入/输出 buffer,让 CPU 填充、GPU 过滤和 CPU 验证在不同 buffer 上重叠执行,可以隐藏传输与执行延迟。
这种管线的收益依赖输入分布。没有 tag 命中时,GPU 可以连续处理大块数据,原文测量中最高获得 27 倍于 CPU 参考路径的加速;命中率升高后,结果回读与 CPU 验证会吞掉收益。这里的数字属于特定旧硬件和测试条件,不应当当成今天所有 GPU 的固定承诺。
6. 用 GPU Virus Filter Lab 观察吞吐如何被候选拖慢
GPU Gems 3 · Chapter 35
GPU Virus Filter Lab
可交互调节签名表规模、输入 buffer、命中率、CPU 验证策略和 buffer 管线,观察候选数量与回读成本如何移动。
overlapping windows8,388,607
candidate ids481,964
CPU checks134,950
relative filter path17×
先保持 30,000 signatures、8 MB buffer 和 rotating buffers,把命中率从 0% 拉到 50%;再切换 full verification、block threshold 和 object threshold。你应该看到:过滤阶段仍然规则,但 candidate ids、结果搬运和 CPU checks 会随着命中密度上升。
当数据库切到 120,000 时,Lab 会显式显示 table overflow。这个状态不是“GPU 变慢一点”这么简单,而是告诉你原始单 key 表的表示能力已被超过:必须引入多候选结构、分层查表或让 CPU 承担更多消歧工作。
小结
- GPU 适合做规则、短路径的候选过滤,CPU 仍负责完整签名验证和对象报告。
- intrapacket 与 interpacket 是两种数据并行布局,连续 buffer 让 GPU 可以暂时忽略对象边界。
- 2-byte key 索引约 64,000-entry 表,tag comparison 用四个前置字节快速排除多数窗口。
- 重叠窗口和跨 buffer 前缀复制防止签名在边界处漏检;命中结果写入
2i 的稀疏输出位置。
- 多 buffer 轮转能隐藏延迟,但 false positive 密集时回读和 CPU 验证仍会成为瓶颈。
练习
练习
问题 1|修改 Demo 代码。 在 Lab 中保持 30,000 signatures,分别使用 0%、10%、50% tag match rate,比较 serial 与 rotating buffers 的 relative filter path、candidate ids 和 CPU checks。说明为什么 GPU 速度不等于端到端速度。
0% 命中时输出 buffer 几乎为空,GPU 可以连续过滤,rotating buffers 主要隐藏输入传输;命中率升高后,candidate ids 增多,结果回读、扫描和完整签名验证占据更多时间。serial 会暴露 CPU/GPU 的等待,而 rotating buffers 只能重叠独立 buffer 的工作,不能消除候选本身的验证成本。
问题 2|诊断边界漏检。 一个签名在 buffer A 的最后三个字节和 buffer B 的前几个字节拼成完整匹配,但系统只在 B 开头重新开始扫描。请指出数据准备和 offset 映射需要怎样修改。
在提交 B 前复制足够覆盖最长签名的 A 尾部字节,让 B 的 GPU 输入拥有连续上下文;扫描输出仍按 B 的局部 offset 写入,CPU 映射对象结果时要减去复制前缀长度,避免把补齐字节报告成新对象内容。对象描述必须保留原始边界,GPU 不负责判断文件归属。
问题 3|场景选型。 一个几乎没有命中的高速流量、一个命中率高但允许提前标记 suspect 的网关、一个必须精确处理正则签名的离线扫描器,分别选择管线和验证策略。
低命中高速流量适合 rotating buffers,让 GPU 过滤吞吐成为主收益;高命中且允许保守告警的网关可以按 block 或 object threshold 提前停止部分 CPU 验证,但要明确 false positive 语义;必须精确执行复杂正则的离线扫描器仍应把 GPU 当候选生成器,保留 CPU 状态机和完整验证,不能把固定字符 key 表当成完整正则引擎。
名词解释
本章出现的专业名词,用大白话再讲一遍。
- signature matching
把输入字节与已知模式逐段比较,并最终确认是否真的匹配的过程。
- intrapacket scanning
在一个文件或数据包内部,把相邻字节窗口分给多个线程并行检查。
- interpacket scanning
同时处理多个文件或数据包,让不同对象占据不同的并行工作单元。
- 2-byte key
用来索引 GPU 签名表的连续两个字节,原始结构约有 64,000 个位置。
- tag filter
跟随 key 存储的四个前置字节比较,用便宜的短检查筛出候选。
- false positive
过滤器报告了候选,但 CPU 验证完整签名后发现并没有真正匹配。