VCD/FST 波形解析库性能对比

Waveform Parser Library Benchmark

11
Libraries / 解析库
3
Languages / 语言
220
Benchmarks / 测试记录

Waveform Formats

波形文件格式解读

VCD — Value Change Dump

IEEE 1364 标准 · 纯文本格式 · 逐行记录值变化

VCD 是 Verilog 仿真器的标准输出格式。它是纯 ASCII 文本,由 Header(元数据+信号定义)和 Body(时间戳+值变化)两部分组成。优点是人类可读、通用兼容;缺点是冗余度极高,176MB 的 VCD 压缩成 FST 仅 12.8MB(~14:1)。

/* ---- Header: 元数据 + 信号层级定义 ---- */
$timescale 1ns $end
$scope module top $end
  $var wire 1 ! clk $end        // '!' 是信号的短ID
  $var wire 8 " data[7:0] $end  // '"' 对应 8-bit 总线
$upscope $end
$enddefinitions $end

/* ---- Body: 时间戳 + 值变化(仅记录变化的信号) ---- */
#0                // 时间 = 0ns
0!               // clk = 0    (标量: 值+ID)
b00000000 "      // data = 0x00 (向量: b+二进制 空格 ID)
#5                // 时间 = 5ns
1!               // clk = 1
#10
0!               // clk = 0
b11111111 "      // data = 0xFF
编码方式
纯 ASCII 文本,每行一个事件
压缩
无内置压缩,需外部 gzip
随机访问
不支持(必须从头扫描)
信号过滤
无(必须读取全部数据)

FST — Fast Signal Trace

GTKWave 原创 · 二进制分块格式 · 内置压缩+索引

FST 由 GTKWave 作者 Tony Bybell 设计,专为高效存储和随机访问优化。它将数据分成固定大小的时间块,每块独立压缩(zlib/LZ4/FastLZ),并维护索引表。支持按信号ID和时间范围精确读取,无需扫描整个文件。

/* FST 是二进制格式,以下为逻辑结构示意 */

[Header Block]
  magic: 0x1B465354     // "FST" 魔数
  start_time: 0          // 起始时间戳
  end_time: 1000000      // 结束时间戳
  num_vars: 2000         // 信号总数

[Hierarchy Block] // gzip 压缩
  scope "top" { var wire 1 "clk"; var wire 8 "data"; }

[Value Change Block #0] // LZ4 压缩
  time_section: [0, 5, 10, ...]   // 本块时间戳数组
  signal "clk":  [0, 1, 0, ...]  // 1-bit: 2bit编码
  signal "data": [00, .., FF]     // 动态别名去重

[Value Change Block #1] // 独立压缩
  ...                              // 可跳过不需要的块

[Geometry Block] // 索引表
  block_offsets: [0x100, 0x8000, ...] // 各块文件偏移
编码方式
二进制分块,单bit信号仅2bit
压缩
内置 zlib/LZ4/FastLZ,~14:1
随机访问
支持(块索引+信号过滤)
信号过滤
BitMask 按需读取指定信号

Library Comparison

库全面对比

Library Language
语言
Format
格式
Read/Write
读/写
Multi-thread
多线程
I/O Model
I/O 模型
API Style
API 风格
Dependencies
依赖

Benchmark Methodology

每项测试运行 3 次取平均值 · 记录标准差与峰值内存

📦

full_parse — 全量解析

将整个波形文件从磁盘加载到内存,解析文件头(信号定义、时间精度)和全部值变化数据,构建完整的内存数据结构。这是最重的操作,衡量库的端到端解析能力。

INPUT / 输入
bench_large.vcd (176 MB, 2000 signals, 200K timesteps)
OUTPUT / 输出
内存中完整的波形数据结构,包含:
  • 2000 个信号的层级树 (hierarchy)
  • 每个信号的全部 (timestamp, value) 对
  • 时间精度、scope 作用域等元数据
# Python (vcdvcd) 示例
vcd = VCDVCD("bench_large.vcd")          # ← 这一行就是 full_parse
# 返回后: vcd.data 包含所有信号的全部值变化

// Rust (wellen) 示例
let wave = simple::read("bench_large.vcd")?;  // ← mmap + rayon 多线程解析
// 返回后: wave.hierarchy() + wave.source() 包含全部数据
📋

signal_list — 信号枚举

解析文件并提取所有信号的名称和层级路径,不读取值变化数据。对于 VCD,需要扫描 header 部分的 $scope/$var 定义;对于 FST,读取 Hierarchy Block 即可。

INPUT / 输入
bench_large.vcd
OUTPUT / 输出
["top.clk", "top.rst_n", "top.cpu.pc[31:0]",
 "top.cpu.alu.result[31:0]", "top.mem.addr[15:0]",
 ... // 共 2000 个信号路径]

time_range — 时间范围

获取波形文件的起始和结束时间戳。仅 Python 端单独测试,Rust 端将此操作合并进 pipeline。对于 FST 格式,时间范围存储在 header 中可瞬时读取;VCD 格式需要扫描到最后一个时间戳。

INPUT / 输入
已加载的波形对象
OUTPUT / 输出
(start=0, end=199999) 时间单位由 timescale 决定
🔍

value_query — 值查询

选取特定信号,读取其全部值变化记录。这是波形查看器最核心的操作。注意:Python 端和 Rust 端的查询规模不同,同语言内纵向对比更有意义。

Python 端
选取 3 个信号,分别在信号列表的 10%/50%/100% 位置,查询各自的值变化
Rust 端
选取 10 个信号,查询全时间范围的全部值变化
OUTPUT 示例 / 输出
signal "top.clk" value changes:
  t=0     → 0
  t=5     → 1
  t=10    → 0
  t=15    → 1
  ... // 共 200K 个值变化点

pipeline — 端到端连续操作

模拟真实使用场景的完整工作流:加载文件 → 枚举信号 → 获取时间范围 → 查询值。一次性完成,衡量库在实际使用中的综合性能。对于「先缓存再操作」的库(如 wellen),后续步骤几乎零开销;对于「每次重新 I/O」的库(如 vcdvcd),每步都重新扫描文件。

# pipeline 伪代码
wave = load("bench_large.vcd")              # Step 1: full_parse
signals = wave.get_signal_list()            # Step 2: signal_list
(t_start, t_end) = wave.get_time_range()    # Step 3: time_range
values = wave.query(signals[0:10], t_start, t_end)  # Step 4: value_query
# 计时: 从 load 开始到 query 结束的总耗时
Test Environment: Linux 6.6.87 (WSL2) · Python 3.13.5 · Rust 2021 edition
Test Files: small (44KB VCD / 5KB FST) · medium (637KB~14MB VCD / 58KB~583KB FST) · large (176MB VCD / 12.8MB FST)

Benchmark Results

性能测试结果 — 最快的库为基准 1x,其余显示相对倍数

Full Parse Throughput (MB/s)

All Operations (ms, relative)

Library Language full_parse signal_list value_query pipeline

Key Findings

关键发现

wellen: 多线程碾压

wellen 通过 mmap + rayon并行 + wavemem LZ4 压缩存储,达到 1.2 GB/s VCD 吞吐量,比单线程 rust-vcd 快 ~10x。

三层架构叠加:mmap 零系统调用 + rayon 分块并行 + 紧凑编码减少内存带宽压力

vcd-ng: 两面性

vcd-ng 内含两套完全不同的解析器:标准 Parser(full_parse: 38.6s)和 FastFlow 零拷贝解析器(value_query: 163ms),差距 237 倍。

标准 Parser 用 io::Bytes 逐字节读取;FastFlow 用 1MB 缓冲区零拷贝解析值变化

FST: 延迟加载 vs 真实解析

wellen FST full_parse 的 "9 GB/s" 实为延迟加载(仅读 header),真实全量解析:fstapi 277ms, fst-reader 316ms。

wellen 源码注释: "fst never reads the full body (unless all signals are displayed)"

缓存 vs 重读:架构决定性能

wellen/pywellen 一次加载,后续零 I/O;vcdvcd 每次操作都要重新扫描文件。这使得 pipeline 中差距比单次操作更悬殊。

vcdvcd pipeline (6.77s) ≈ full_parse (6.88s),因为每步都在重新扫描 176MB 文件

Recommendations

推荐选择

Use Case / 使用场景 Recommended Reason / 理由
Rust 波形查看器后端wellenVCD+FST+GHW 统一接口,多线程,Surfer 生产验证
Rust 通用 VCD 读写rust-vcd零依赖,Iterator 流式 API,8 位贡献者
Rust 高性能 VCD 值过滤vcd-ng (FastFlow)零拷贝解析,2bit/值编码,数据段专用
Rust 纯 FST 读写fst-reader + fst-writer纯 Rust,零 C 依赖,proptest 验证
Rust 完整 FST 功能fstapiGTKWave C FFI,并行写入,mmap
Python VCD 快速脚本vcdvcd零依赖,pip install,Pythonic API
Python FST 处理pylibfstCFFI C 绑定,C 级性能
C/C++ EDA 嵌入gtkwave/libfst业界标准,6 文件独立编译