jdb_pgm 0.3.5

Ultra-fast single-threaded Pgm-Index optimized for thread-per-core architecture / 专为单线程一核架构优化的超快 Pgm 索引
Documentation

English | 中文


jdb_pgm : Ultra-fast Learned Index for Sorted Keys

A highly optimized, single-threaded Rust implementation of the Pgm-index (Piecewise Geometric Model index), designed for ultra-low latency lookups and minimal memory overhead.

Benchmark


Introduction

jdb_pgm is a specialized reimplementation of the Pgm-index data structure. It approximates the distribution of sorted keys using piecewise linear models, enabling search operations with O(log ε) complexity.

This crate focuses on single-threaded performance, preparing for a "one thread per CPU" architecture. By removing concurrency overhead and optimizing memory layout (e.g., SIMD-friendly loops), it achieves statistically significant speedups over standard binary search and traditional tree-based indexes.

Usage

Add this to your Cargo.toml:

[dependencies]
jdb_pgm = "0.3"

Two Modes

Pgm<K> - Core index without data ownership (ideal for SSTable, mmap scenarios)

use jdb_pgm::Pgm;

fn main() {
  let data: Vec<u64> = (0..1_000_000).collect();
  
  // Build index from data reference
  let pgm = Pgm::new(&data, 32, true).unwrap();
  
  // Get predicted search range
  let (start, end) = pgm.predict_range(123_456);
  
  // Search in your own data store
  if let Ok(pos) = data[start..end].binary_search(&123_456) {
    println!("Found at index: {}", start + pos);
  }
}

PgmData<K> - Index with data ownership (convenient for in-memory use)

use jdb_pgm::PgmData;

fn main() {
  let data: Vec<u64> = (0..1_000_000).collect();
  
  // Build index and take ownership of data
  let index = PgmData::load(data, 32, true).unwrap();
  
  // Direct lookup
  if let Some(pos) = index.get(123_456) {
    println!("Found at index: {}", pos);
  }
}

Feature Flags

  • data (default): Enables PgmData struct with data ownership
  • bitcode: Enables serialization via bitcode
  • key_to_u64: Enables key_to_u64() helper for byte keys

Performance

Based on internal benchmarks with 1,000,000 u64 keys (jdb_pgm's Pgm does not own data, memory is index-only):

  • ~2.3x Faster than standard Binary Search (17.85ns vs 40.89ns).
  • ~1.1x - 1.3x Faster than pgm_index (17.85ns vs 20.13ns).
  • ~4.7x Faster than BTreeMap (17.85ns vs 84.21ns).
  • ~2.2x Faster than HashMap (17.85ns vs 39.99ns).
  • 1.01 MB Index Memory for ε=32 (pgm_index uses 8.35 MB).
  • Prediction Accuracy: jdb_pgm max error equals ε exactly, pgm_index max error is 8ε.

🆚 Comparison with pgm_index

This crate (jdb_pgm) is a specialized fork/rewrite of the original concept found in pgm_index. While the original library aims for general-purpose usage with multi-threading support (Rayon), jdb_pgm takes a different approach:

Key Differences Summary

Feature jdb_pgm pgm_index
Threading Single-threaded Multi-threaded (Rayon)
Segment Building Shrinking Cone O(N) Parallel Least Squares
Prediction Model slope * key + intercept (key - intercept) / slope
Prediction Accuracy ε-bounded (guaranteed) Heuristic (not guaranteed)
Memory Arc-free, zero-copy Arc<Vec> wrapper
Data Ownership Optional (Pgm vs PgmData) Always owns data
Dependencies Minimal rayon, num_cpus, num-traits

1. Architectural Shift: Single-Threaded by Design

The original pgm_index introduces Rayon for parallel processing. However, in modern high-performance databases (like ScyllaDB or specialized engines), the thread-per-core architecture is often superior.

  • One Thread, One CPU: We removed all locking, synchronization, and thread-pool overhead.
  • Deterministic Latency: Without thread scheduling jitter, p99 latencies are significantly more stable.

2. Segment Building Algorithm

jdb_pgm: Shrinking Cone (Optimal PLA)

// O(N) streaming algorithm with guaranteed ε-bound
while end < n {
  slope_lo = (idx - first_idx - ε) / dx
  slope_hi = (idx - first_idx + ε) / dx
  if min_slope > max_slope: break  // cone collapsed
  // Update shrinking cone bounds
}
slope = (min_slope + max_slope) / 2

pgm_index: Parallel Least Squares

// Divides data into fixed chunks, fits each with least squares
target_segments = optimal_segment_count_adaptive(data, epsilon)
segments = (0..target_segments).par_iter().map(|i| {
  fit_segment(&data[start..end], start)  // least squares fit
}).collect()

The shrinking cone algorithm guarantees that prediction error never exceeds ε, while least squares fitting provides no such guarantee.

3. Prediction Formula

jdb_pgm: pos = slope * key + intercept

  • Direct forward prediction
  • Uses FMA (Fused Multiply-Add) for precision

pgm_index: pos = (key - intercept) / slope

  • Inverse formula (solving for x given y)
  • Division is slower than multiplication
  • Risk of division by zero when slope ≈ 0

4. Core Implementation Upgrades

While based on the same Pgm theory, our implementation details are significantly more aggressive:

  • Eliminating Float Overhead: We replaced expensive floating-point rounding operations (round/floor) with bitwise-based integer casting (as isize + 0.5), bringing a qualitative leap in instruction cycles.
  • Transparent to Compiler: The core loops are refactored to remove dependencies that block LLVM's auto-vectorization, generating AVX2/AVX-512 instructions without manual intrinsic code.
  • Reducing Branch Misprediction: We rewrote the predict and search phases with manual clamping and branchless logic, drastically reducing pipeline stalls.

5. Allocation Strategy

  • Heuristic Pre-allocation: The build process estimates segment count (N / 2ε) ahead of time, effectively eliminating vector reallocations during construction.
  • Zero-Copy: Keys (especially integers) are handled without unnecessary cloning.

Features

  • Single-Threaded Optimization: Tuned for maximum throughput on a dedicated core.
  • Zero-Copy Key Support: Supports u8, u16, u32, u64, i8, i16, i32, i64.
  • Predictable Error Bounds: The epsilon parameter strictly controls the search range.
  • Vectorized Sorting Check: Uses SIMD-friendly sliding windows for validation.
  • Flexible Data Ownership: Pgm for external data, PgmData for owned data.

Design

The index construction and lookup process allows for extremely fast predictions of key positions.

graph TD
    subgraph Construction
    A[Sorted Data] -->|build_segments| B[Linear Segments]
    B -->|build_lut| C[Look-up Table]
    end

    subgraph Query
    Q[Search Key] -->|find_seg| S[Select Segment]
    S -->|predict| P[Approximate Pos]
    P -->|binary_search| F[Final Position]
    end

    C -.-> S
    B -.-> S
  1. Construction: The dataset is scanned to create Piecewise Linear Models (segments) that approximate the key distribution within an error ε.
  2. Lookup Table: A secondary structure (LUT) allows O(1) access to the correct segment.
  3. Query:
    • Find the relevant segment using the key.
    • Predict the approximate position using the linear model slope * key + intercept.
    • Perform a small binary search within the error bound [pos - ε, pos + ε].

Technology Stack

  • Core: Rust (Edition 2024)
  • Algorithm: Pgm-Index (Piecewise Geometric Model)
  • Testing: aok, static_init, criterion (for benchmarks)

Directory Structure

jdb_pgm/
├── src/
│   ├── lib.rs      # Exports and entry point
│   ├── pgm.rs      # Core Pgm struct (no data ownership)
│   ├── data.rs     # PgmData struct (with data ownership)
│   ├── build.rs    # Segment building algorithm
│   ├── types.rs    # Key trait, Segment, PgmStats
│   ├── consts.rs   # Constants
│   └── error.rs    # Error types
├── tests/          # Integration tests
├── benches/        # Criterion benchmarks
└── examples/       # Usage examples

API Reference

Pgm<K> (Core, no data ownership)

  • new(data: &[K], epsilon: usize, check_sorted: bool) -> Result<Self> Constructs the index from a data slice. Index does not own the data.

  • predict(key: K) -> usize Returns the predicted position for a key.

  • predict_range(key: K) -> (usize, usize) Returns the search range [start, end) for a key.

  • segment_count() -> usize Returns the number of segments.

  • mem_usage() -> usize Returns memory usage of the index (excluding data).

PgmData<K> (With data ownership, requires data feature)

  • load(data: Vec<K>, epsilon: usize, check_sorted: bool) -> Result<Self> Constructs the index and takes ownership of data.

  • get(key: K) -> Option<usize> Returns the index of the key if found, or None.

  • get_many(keys: I) -> Iterator Returns an iterator of results for batch lookups.

  • stats() -> PgmStats Returns internal statistics like segment count and memory usage.

  • All Pgm methods are available via Deref.

History

In the era of "Big Data," traditional B-Trees became a bottleneck due to their memory consumption and cache inefficiency. In 2020, Paolo Ferragina and Giorgio Vinciguerra introduced the Piecewise Geometric Model (Pgm) index. Their key insight was simple yet revolutionary: why store every key when the data's distribution often follows a predictable pattern?

By treating the index as a machine learning problem—learning the CDF of the data—they reduced the index size by orders of magnitude while maintaining O(log N) worst-case performance. This project, jdb_pgm, takes that concept and strips it down to its bare metal essentials for Rust, prioritizing raw speed on modern CPUs where every nanosecond counts.

Bench

Pgm-Index Benchmark

Performance comparison of Pgm-Index vs Binary Search with different epsilon values.

Data Size: 1,000,000

Algorithm Epsilon Mean Time Std Dev Throughput Memory
jdb_pgm 32 17.85ns 58.01ns 56.01M/s 1.01 MB
jdb_pgm 64 17.91ns 56.67ns 55.83M/s 512.00 KB
pgm_index 32 20.13ns 54.58ns 49.67M/s 8.35 MB
pgm_index 64 23.16ns 66.31ns 43.18M/s 8.38 MB
pgm_index 128 25.91ns 62.66ns 38.60M/s 8.02 MB
jdb_pgm 128 26.15ns 96.65ns 38.25M/s 256.00 KB
HashMap null 39.99ns 130.55ns 25.00M/s 40.00 MB
Binary Search null 40.89ns 79.06ns 24.46M/s -
BTreeMap null 84.21ns 99.32ns 11.87M/s 16.83 MB

Accuracy Comparison: jdb_pgm vs pgm_index

Data Size Epsilon jdb_pgm (Max) jdb_pgm (Avg) pgm_index (Max) pgm_index (Avg)
1,000,000 128 128 46.80 1024 511.28
1,000,000 32 32 11.35 256 127.48
1,000,000 64 64 22.59 512 255.39

Build Time Comparison: jdb_pgm vs pgm_index

Data Size Epsilon jdb_pgm (Time) pgm_index (Time) Speedup
1,000,000 128 1.28ms 1.26ms 0.98x
1,000,000 32 1.28ms 1.27ms 0.99x
1,000,000 64 1.28ms 1.20ms 0.94x

Configuration

Query Count: 1500000 Data Sizes: 10,000, 100,000, 1,000,000 Epsilon Values: 32, 64, 128


Epsilon (ε) Explained

Epsilon (ε) controls the accuracy-speed trade-off:

Mathematical definition: ε defines the maximum absolute error between the predicted position and the actual position in the data array. When calling load(data, epsilon, ...), ε guarantees |pred - actual| ≤ ε, where positions are indices within the data array of length data.len().

Example: For 1M elements with ε=32, if the actual key is at position 1000:

  • ε=32 predicts position between 968-1032, then checks up to 64 elements
  • ε=128 predicts position between 872-1128, then checks up to 256 elements

Notes

What is Pgm-Index?

Pgm-Index (Piecewise Geometric Model Index) is a learned index structure that approximates the distribution of keys with piecewise linear models. It provides O(log ε) search time with guaranteed error bounds, where ε controls the trade-off between memory and speed.

Why Compare with Binary Search?

Binary search is the baseline for sorted array lookup. Pgm-Index aims to:

  • Match or exceed binary search performance
  • Reduce memory overhead compared to traditional indexes
  • Provide better cache locality for large datasets

Environment

  • OS: macOS 26.1 (arm64)
  • CPU: Apple M2 Max
  • Cores: 12
  • Memory: 64.0GB
  • Rust: rustc 1.94.0-nightly (8d670b93d 2025-12-31)

References


About

This project is an open-source component of js0.site ⋅ Refactoring the Internet Plan.

We are redefining the development paradigm of the Internet in a componentized way. Welcome to follow us:


jdb_pgm : 面向排序键的超快学习型索引

一个经过高度优化的 Rust 版 Pgm 索引(分段几何模型索引)单线程实现,专为超低延迟查找和极小的内存开销而设计。

性能评测


简介

jdb_pgm 是 Pgm-index 数据结构的专用重构版本。它使用分段线性模型近似排序键的分布,从而实现 O(log ε) 复杂度的搜索操作。

本 crate 专注于 单线程性能,为"一线程一核 (One Thread Per CPU)"的架构做准备。通过移除并发开销并优化内存布局(如 SIMD 友好的循环),与标准二分查找和传统树状索引相比,它实现了具有统计意义的显著速度提升。

使用方法

Cargo.toml 中添加依赖:

[dependencies]
jdb_pgm = "0.3"

两种模式

Pgm<K> - 核心索引,不持有数据(适用于 SSTable、mmap 场景)

use jdb_pgm::Pgm;

fn main() {
  let data: Vec<u64> = (0..1_000_000).collect();
  
  // 从数据引用构建索引
  let pgm = Pgm::new(&data, 32, true).unwrap();
  
  // 获取预测的搜索范围
  let (start, end) = pgm.predict_range(123_456);
  
  // 在你自己的数据存储中搜索
  if let Ok(pos) = data[start..end].binary_search(&123_456) {
    println!("Found at index: {}", start + pos);
  }
}

PgmData<K> - 持有数据的索引(适用于内存使用场景)

use jdb_pgm::PgmData;

fn main() {
  let data: Vec<u64> = (0..1_000_000).collect();
  
  // 构建索引并获取数据所有权
  let index = PgmData::load(data, 32, true).unwrap();
  
  // 直接查找
  if let Some(pos) = index.get(123_456) {
    println!("Found at index: {}", pos);
  }
}

Feature 标志

  • data(默认):启用持有数据的 PgmData 结构体
  • bitcode:启用 bitcode 序列化
  • key_to_u64:启用 key_to_u64() 辅助函数用于字节键

性能

基于 1,000,000 个 u64 键的内部基准测试(jdb_pgm 的 Pgm 不持有数据,仅统计索引内存):

  • 比标准二分查找 快 ~2.3 倍(17.85ns vs 40.89ns)。
  • pgm_index 快 ~1.1 - 1.3 倍(17.85ns vs 20.13ns)。
  • 比 BTreeMap 快 ~4.7 倍(17.85ns vs 84.21ns)。
  • 比 HashMap 快 ~2.2 倍(17.85ns vs 39.99ns)。
  • ε=32 时,索引内存仅 1.01 MB(pgm_index 为 8.35 MB)。
  • 预测精度:jdb_pgm 最大误差严格等于 ε,pgm_index 最大误差为 8ε。

🆚 与 pgm_index 的对比

本 crate (jdb_pgm) 是原版 pgm_index 概念的一个专用分叉/重写版本。原版库旨在通用并支持多线程(Rayon),而 jdb_pgm 采取了截然不同的优化路径:

核心差异总结

特性 jdb_pgm pgm_index
线程模型 单线程 多线程 (Rayon)
段构建算法 收缩锥 O(N) 并行最小二乘法
预测公式 slope * key + intercept (key - intercept) / slope
预测精度 ε 有界(保证) 启发式(无保证)
内存 无 Arc,零拷贝 Arc<Vec> 包装
数据所有权 可选(Pgm vs PgmData 始终持有数据
依赖 最小化 rayon, num_cpus, num-traits

1. 架构转型:原生单线程设计

原版 pgm_index 引入了 Rayon 进行并行处理。然而,在现代高性能数据库(如 ScyllaDB 或专用引擎)中,线程绑定核心 (Thread-per-Core) 架构往往更具优势。

  • 一线程一 CPU:我们移除了所有的锁、同步原语和线程池开销。
  • 确定的延迟:没有了线程调度的抖动,p99 延迟显著更加稳定。

2. 段构建算法

jdb_pgm: 收缩锥算法 (Optimal PLA)

// O(N) 流式算法,保证 ε 有界
while end < n {
  slope_lo = (idx - first_idx - ε) / dx
  slope_hi = (idx - first_idx + ε) / dx
  if min_slope > max_slope: break  // 锥体收缩至崩塌
  // 更新收缩锥边界
}
slope = (min_slope + max_slope) / 2

pgm_index: 并行最小二乘法

// 将数据分成固定块,对每块进行最小二乘拟合
target_segments = optimal_segment_count_adaptive(data, epsilon)
segments = (0..target_segments).par_iter().map(|i| {
  fit_segment(&data[start..end], start)  // 最小二乘拟合
}).collect()

收缩锥算法保证预测误差永远不超过 ε,而最小二乘拟合无法提供这种保证。

3. 预测公式

jdb_pgm: pos = slope * key + intercept

  • 直接正向预测
  • 使用 FMA(融合乘加)提高精度

pgm_index: pos = (key - intercept) / slope

  • 逆向公式(给定 y 求 x)
  • 除法比乘法慢
  • 当 slope ≈ 0 时有除零风险

4. 核心算法实现升级

虽然基于相同的 Pgm 理论,但在具体代码实现层面上,我们的算法更加激进:

  • 消除浮点开销:我们将所有昂贵的浮点取整操作 (round/floor) 替换为基于位操作的整数转换 (as isize + 0.5),这在指令周期层面带来了质的飞跃。
  • 对编译器透明:核心循环结构经过重构,移除了阻碍 LLVM 自动向量化的依赖,无需编写 intrinsic 代码即可生成 AVX2/AVX-512 指令。
  • 减少分支预测失败:通过手动 clamp 和无分支逻辑重写了 predictsearch 阶段,大幅降低了流水线停顿。

5. 分配策略

  • 启发式预分配:构建过程会提前估算段的数量 (N / 2ε),有效消除了构建过程中的向量重分配 (Reallocation)。
  • 零拷贝:键(尤其是整数)的处理避免了不必要的克隆。

特性

  • 单线程优化:针对专用核心的吞吐量进行了极致调优。
  • 零拷贝支持:支持 u8, u16, u32, u64, i8, i16, i32, i64
  • 可预测的误差界限epsilon 参数严格控制搜索范围。
  • 向量化排序检查:使用 SIMD 友好的滑动窗口进行验证。
  • 灵活的数据所有权Pgm 用于外部数据,PgmData 用于持有数据。

设计

索引构建和查找过程允许极快地预测键的位置。

graph TD
    subgraph Construction [构建]
    A[已排序数据] -->|build_segments| B[线性段模型]
    B -->|build_lut| C[查找表 LUT]
    end

    subgraph Query [查询]
    Q[搜索键] -->|find_seg| S[选择段]
    S -->|predict| P[近似位置]
    P -->|binary_search| F[最终位置]
    end

    C -.-> S
    B -.-> S
  1. 构建: 扫描数据集以创建分段线性模型(Segments),在误差 ε 内近似键的分布。
  2. 查找表: 一个辅助结构(LUT)允许以 O(1) 的时间找到正确的段。
  3. 查询:
    • 使用键找到对应的段。
    • 使用线性模型 slope * key + intercept 预测近似位置。
    • 在误差范围 [pos - ε, pos + ε] 内执行小范围二分查找。

技术栈

  • 核心: Rust (Edition 2024)
  • 算法: Pgm-Index (分段几何模型)
  • 测试: aok, static_init, criterion (用于基准测试)

目录结构

jdb_pgm/
├── src/
│   ├── lib.rs      # 导出和入口点
│   ├── pgm.rs      # 核心 Pgm 结构体(不持有数据)
│   ├── data.rs     # PgmData 结构体(持有数据)
│   ├── build.rs    # 段构建算法
│   ├── types.rs    # Key trait, Segment, PgmStats
│   ├── consts.rs   # 常量
│   └── error.rs    # 错误类型
├── tests/          # 集成测试
├── benches/        # Criterion 基准测试
└── examples/       # 使用示例

API 参考

Pgm<K>(核心,不持有数据)

  • new(data: &[K], epsilon: usize, check_sorted: bool) -> Result<Self> 从数据切片构建索引。索引不持有数据。

  • predict(key: K) -> usize 返回键的预测位置。

  • predict_range(key: K) -> (usize, usize) 返回键的搜索范围 [start, end)

  • segment_count() -> usize 返回段的数量。

  • mem_usage() -> usize 返回索引的内存使用量(不含数据)。

PgmData<K>(持有数据,需要 data feature)

  • load(data: Vec<K>, epsilon: usize, check_sorted: bool) -> Result<Self> 构建索引并获取数据所有权。

  • get(key: K) -> Option<usize> 如果找到,返回键的索引;否则返回 None

  • get_many(keys: I) -> Iterator 返回批量查找的结果迭代器。

  • stats() -> PgmStats 返回内部统计信息,如段数和内存使用情况。

  • 通过 Deref 可访问所有 Pgm 方法。

历史背景

在"大数据"时代,传统的 B-Tree 由于其内存消耗和缓存效率低逐渐成为瓶颈。2020 年,Paolo Ferragina 和 Giorgio Vinciguerra 提出了 分段几何模型 (Pgm) 索引。他们的核心见解简单而具有革命性:如果数据分布通常遵循可预测的模式,为什么还要存储每个键呢?

通过将索引视为一个机器学习问题——学习数据的 CDF(累积分布函数)——他们在保持 O(log N) 最坏情况性能的同时,将索引大小减少了几个数量级。本项目 jdb_pgm 借鉴了这一概念,并将其剥离至最本质的 Rust 实现,在每一纳秒都至关重要的现代 CPU 上优先考虑原始速度。

评测

Pgm 索引评测

Pgm-Index 与二分查找在不同 epsilon 值下的性能对比。

数据大小: 1,000,000

算法 Epsilon 平均时间 标准差 吞吐量 内存
jdb_pgm 32 17.85ns 58.01ns 56.01M/s 1.01 MB
jdb_pgm 64 17.91ns 56.67ns 55.83M/s 512.00 KB
pgm_index 32 20.13ns 54.58ns 49.67M/s 8.35 MB
pgm_index 64 23.16ns 66.31ns 43.18M/s 8.38 MB
pgm_index 128 25.91ns 62.66ns 38.60M/s 8.02 MB
jdb_pgm 128 26.15ns 96.65ns 38.25M/s 256.00 KB
HashMap null 39.99ns 130.55ns 25.00M/s 40.00 MB
二分查找 null 40.89ns 79.06ns 24.46M/s -
BTreeMap null 84.21ns 99.32ns 11.87M/s 16.83 MB

精度对比: jdb_pgm vs pgm_index

数据大小 Epsilon jdb_pgm (最大) jdb_pgm (平均) pgm_index (最大) pgm_index (平均)
1,000,000 128 128 46.80 1024 511.28
1,000,000 32 32 11.35 256 127.48
1,000,000 64 64 22.59 512 255.39

构建时间对比: jdb_pgm vs pgm_index

数据大小 Epsilon jdb_pgm (时间) pgm_index (时间) 加速比
1,000,000 128 1.28ms 1.26ms 0.98x
1,000,000 32 1.28ms 1.27ms 0.99x
1,000,000 64 1.28ms 1.20ms 0.94x

配置

查询次数: 1500000 数据大小: 10,000, 100,000, 1,000,000 Epsilon 值: 32, 64, 128


Epsilon (ε) 说明

Epsilon (ε) 控制精度与速度的权衡:

数学定义:ε 定义了预测位置与实际位置在数据数组中的最大绝对误差。调用 load(data, epsilon, ...) 时,ε 保证 |pred - actual| ≤ ε,其中位置是长度为 data.len() 的数据数组中的索引。

举例说明:对于 100 万个元素,ε=32 时,如果实际键在位置 1000:

  • ε=32 预测位置在 968-1032 之间,然后检查最多 64 个元素
  • ε=128 预测位置在 872-1128 之间,然后检查最多 256 个元素

备注

什么是 Pgm-Index?

Pgm-Index(分段几何模型索引)是一种学习型索引结构,使用分段线性模型近似键的分布。 它提供 O(log ε) 的搜索时间,并保证误差边界,其中 ε 控制内存和速度之间的权衡。

为什么与二分查找对比?

二分查找是已排序数组查找的基准。Pgm-Index 旨在:

  • 匹配或超过二分查找的性能
  • 相比传统索引减少内存开销
  • 为大数据集提供更好的缓存局部性

环境

  • 系统: macOS 26.1 (arm64)
  • CPU: Apple M2 Max
  • 核心数: 12
  • 内存: 64.0GB
  • Rust版本: rustc 1.94.0-nightly (8d670b93d 2025-12-31)

参考


关于

本项目为 js0.site ⋅ 重构互联网计划 的开源组件。

我们正在以组件化的方式重新定义互联网的开发范式,欢迎关注: