
从 gravity、redb 和 klauspost/compress 出发,分别练习把源码编译成字节码、把一次提交安全地落到磁盘、把重复串折叠掉,并给出今晚能跑通的最小路径。
+ 与 || 的绑定力就写在同一张表里。4src/compiler/ 里每一趟各占一个文件,按文件名就能排出顺序。4List.sorted() 与 List.map() 把结果列表的容量覆写成源列表的容量,越出分配区写;String 的 UTF-8 处理按首字节推测序列长度却不检查剩余字节,还能让 String.split("") 永不前进;JSON.parse 不检查字段的 JSON 类型就去读联合体成员。1git clone --depth 1 --branch 0.9.9 https://github.com/marcobambini/gravity.git
cd gravity && make
./gravity -i 'return 2 + 3'RESULT: (INT) 5 (in 0.0002 ms)./gravity -t test/unittestTotal Tests: 356
Total Successes: 356
Total Failures: 0
Result: 100.00 %
Time: 1519.9628 ms./gravity -i 'func f() { var foo = 7; return true ? foo : 1 } return f()',得到 7。6 把仓库切回 0.9.8 重新 make,同一条命令给出的是 true,也就是条件本身;把两边的顺序反过来写,0.9.8 又会吐出分支里那个变量的值。./gravity -c demo.gravity 写出一个 841 字节的 gravity.g,再用 ./gravity -x gravity.g 执行它。5fib(28) 返回 317811,耗时 40.3 毫秒;一个 100 万次的累加循环是 43.7 毫秒。src/compiler/ 的第一个文件读起,读完一趟就对照一次同名的 .c 文件。4JSON.parse,这一版更值得换上去。1for (var i in 0..<5),写成 C 风格的 for (init; cond; incr) 会报 Expected in but found ;。./gravity 只会打印一行 REPL not yet implemented.。Option 标签字节的 NICHE 机制,稳定了两组游标 API;修的是一批和崩溃恢复、页缓存、compact() 有关的问题。7god byte 第一位记着最新提交在槽 0 还是槽 1,第二位要求打开时先做修复,第三位说明主槽是两阶段提交写下的。8 redb 依赖「这是一个字节」来完成原子提交。8512 字节超级头
├─ 数据库头部(64 字节)
│ ├─ 魔数 9 字节('redb' + 0x1A 0A A9 0D 0A)
│ ├─ god byte 1 字节
│ │ ├─ bit 0 primary_bit:最新提交在槽 0 还是槽 1
│ │ ├─ bit 1 recovery_required:打开时要先走修复
│ │ └─ bit 2 two_phase_commit:主槽用两阶段提交写入
│ ├─ 页大小 4 字节、区域头页数、区域最多数据页、完整区域数……
│ └─ 补齐到 64 字节
├─ 提交槽 0(128 字节)
│ ├─ 文件格式版本 1 字节
│ ├─ 三个布尔:用户根页 / 系统根页 / 空闲树根页是否非空
│ ├─ 用户根页 8 字节 + 校验和 16 字节 + 表数量 8 字节
│ ├─ 系统根页 8 字节 + 校验和 16 字节 + 表数量 8 字节
│ └─ 最后一次提交的事务 id 8 字节 + 槽校验和 16 字节
└─ 提交槽 1(128 字节,与槽 0 同布局)curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo new kv && cd kv && cargo add redb@4.4.0rustup-init 写到 /tmp 再执行,/tmp 如果是 noexec 挂载就会失败——本机就是这么失败的。把文件下到工作目录再运行即可。Durability 那一行:use redb::{Database, Durability, TableDefinition};
use std::time::Instant;
const TABLE: TableDefinition<u64, u64> = TableDefinition::new("data");
fn main() -> Result<(), redb::Error> {
let db = Database::create("demo.redb")?;
let t0 = Instant::now();
for i in 0..2000u64 {
let mut txn = db.begin_write()?;
txn.set_durability(Durability::None)?; // 换成 Durability::Immediate 再跑一次
{
let mut t = txn.open_table(TABLE)?;
t.insert(i, &i)?;
}
txn.commit()?;
}
println!("2000 single-key commits: {:?}", t0.elapsed());
Ok(())
}2000 single-key commits, Durability::none: 17.073244ms
2000 single-key commits, Durability::immediate: 2.44252294sNone 是 17 毫秒,Immediate 是 2.44 秒,差大约 150 倍,差的就是那一次 fsync。txn.ephemeral_savepoint()?,再打开表追加 1000 个键,然后回滚并提交;本机的表长度从 2000 回到 1000。顺序反过来——先打开表再创建存档点——拿到的是 SavepointError::InvalidSavepoint,所以存档点要在事务「变脏」之前创建。check_integrity() 不再在「一个把文件变大的事务被中止」之后误报「已修复损坏」,页缓存在写负载下不再无界增长。7Option 的编码,声明前后的 Option<T> 不是同一个类型,用旧类型写过的表再打开会报 TableTypeMismatch,而不是被误读。7experimental-multiprocess 特性另选模式。8 5.0.0 已经在 CHANGELOG 里排着,内容是 no_std 构建和 range 系列 API 的签名调整。10newFastEnc,每个等级一份 level1.go 到 level6.go,各自带着不同的哈希表;等级 7 到 9 换成懒匹配那条路径;负数等级用来指定自定义窗口大小。14BestSpeed,9 是 BestCompression,默认值 -1 实为 5。14nounsafe 关掉所有 unsafe 用法,noasm 关掉全部汇编。14go.mod 里有一条 retract,撤掉 v1.14.1 到 v1.14.3 和 v1.18.1,每条都带一个 issue 或 PR 链接。15ReadDir 现在拒绝名为 . 的条目,否则 fs.WalkDir 会一直转下去;同一批改动里还有 S2 对短索引块的拒绝。12go get github.com/klauspost/compress@latest 一条命令,本机用 go1.27.2 取到 v1.20.3。14 它是标准库同名包的替换件,改一行 import 就换完。14go mod init bench && go get github.com/klauspost/compress@v1.20.3// 和标准库同名同形:只换 import 路径和包名
import kgzip "github.com/klauspost/compress/gzip"
import "github.com/klauspost/compress/zstd"
w, _ := kgzip.NewWriterLevel(&buf, 6) // 标准库这里是 compress/gzip
w.Write(data)
w.Close()
enc, _ := zstd.NewWriter(nil, zstd.WithEncoderLevel(zstd.SpeedDefault))
comp := enc.EncodeAll(data, make([]byte, 0, len(data)/3))| 编码器 | 压缩耗时 | 压缩后 | 压缩比 | 解压耗时 |
|---|---|---|---|---|
标准库 compress/gzip,flate 等级 6 | 151.5 ms | 4.1 MiB | 4.60× | 71.0 ms |
klauspost gzip,flate 等级 6 | 126.9 ms | 4.1 MiB | 4.60× | 56.3 ms |
klauspost zstd,SpeedFastest | 53.3 ms | 3.7 MiB | 5.22× | 16.4 ms |
klauspost zstd,SpeedDefault | 65.0 ms | 3.3 MiB | 5.82× | 13.0 ms |
klauspost zstd,SpeedBestCompression | 397.7 ms | 2.7 MiB | 6.98× | 13.1 ms |
compress/flate 换成这个同名包再跑一次自己的基准。本机的 gzip 等级 6 从 151.5 毫秒降到 126.9 毫秒,解压从 71.0 降到 56.3。14SpeedDefault 和 SpeedBestCompression:本机后者慢 6 倍,压缩后只小 20%,这就是那三个数字要你做的取舍。flate/level1.go 和等级 7 以上的实现,看最快那档在找重复串时省掉了什么。go.mod。15| 想练的概念 | 仓库与本期信号 | 最小条件 | 第一条可观察路径 | 主要限制 |
|---|---|---|---|---|
| 编译原理:源码怎么变成字节码 | gravity · 0.9.9 北京时间 10 月 11 日晚发布 | 一个 C99 编译器加 make | make 之后跑 ./gravity -i 'return 2 + 3' | 语法自成一系,没有交互式解释器。(架构文档) |
| 数据库:B+ 树与崩溃一致性 | redb · 4.4.0 北京时间 10 月 14 日凌晨发布 | rustup 加一条 cargo add redb | 单键提交循环在 None 与 Immediate 下各跑一次 | 默认只有一个写者;存档点要在事务变脏之前创建。(设计文档) |
| 数据压缩:LZ77 与霍夫曼编码 | klauspost/compress · v1.20.3 北京时间 10 月 12 日晚发布 | go get 一条命令 | 把标准库 gzip 换成它,再压一次同一份语料 | 需要 Go 工具链;本版性能改动集中在 arm64。(仓库说明) |
src/compiler/ 的第一个文件读起,redb 值得从设计文档的提交策略一节读起,klauspost/compress 值得从 flate/level1.go 读起。github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.