
从 fast_float、fxamacker/cbor 和 coregex 出发,分别练习浮点数的正确舍入、二进制解码器的上限设计,以及正则引擎的线性时间保证,并给出今晚能跑通的最小路径。
integer_times_pow10 里的有符号取负溢出,10 月 7 日早上把 pow10 的 int64 下界补齐。56 三处改动都落在同一件事上:把一个十进制数变成二进制浮点数,边界那条路不能出错。fast_float::from_chars(first, last, value) 把一段字符解析成 float 或 double,返回的 from_chars_result 里,ptr 指向解析停下的位置,ec 是错误码。7strtod 的分界:strtod 跟着进程的 locale 走,还会写 errno。7git clone --depth 1 https://github.com/fastfloat/fast_float.git// demo.cpp
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "fast_float/fast_float.h"
int main() {
const char* s = "3.141592653589793";
double a = 0;
auto r = fast_float::from_chars(s, s + strlen(s), a);
printf("fast_float: %.17g (停在第 %zu 个字符, ec=%d)\n", a, (size_t)(r.ptr - s), (int)r.ec);
printf("strtod : %.17g\n", strtod(s, nullptr));
}g++ -std=c++17 -I fast_float/include -O2 demo.cpp -o demo && ./demofast_float: 3.1415926535897931 (停在第 17 个字符, ec=0)
strtod : 3.1415926535897931"0.1" 分别喂给 from_chars 和 strtod,用 %.20g 打印。本机两边都是 0.10000000000000000555,也就是同一个二进制数。7from_chars、一边用 strtod 循环解析并计时。本机(g++ 14、-O2)跑出来是 9 毫秒对 61 毫秒。1e9999 和 1e-9999,检查 ec 是不是 std::errc::result_out_of_range,值是不是变成了正无穷和零。7master(上面两个提交已经在上面)。1 许可三选一:Apache-2.0、MIT 或 Boost。7 四条限制先知道:只支持 float 和 double,没有 long double;只认十进制,不认十六进制字面量;1e9999 这类会给回正负无穷并把 ec 设成 result_out_of_range;库假设当前舍入模式是 FE_TONEAREST。7MaxNestedLevels 默认 32、MaxArrayElements 默认 131072、MaxMapPairs 各有默认值,三者都可以用 DecOptions 调。12 本机拿一个声明长度是 4294967295 的数组去解,报的是 exceeded max number of elements 131072 for CBOR array——解码器读完头部就拒绝,不会先按长度分配。encoding/gob 举例——181 字节的数据就能让它抛 out of memory。encoding/gob 的文档也写明它没有针对对抗性输入加固。1213go get 一条命令,一个 main.go 就能跑。mkdir cbor-demo && cd cbor-demo && go mod init demo
go get github.com/fxamacker/cbor/v2package main
import (
"fmt"
cbor "github.com/fxamacker/cbor/v2"
)
type S struct {
Name string `cbor:"name"`
N int `cbor:"n"`
}
func main() {
b, _ := cbor.Marshal(S{"frank", 42})
fmt.Printf("编码 %d 字节: % x\n", len(b), b)
var out S
cbor.Unmarshal(b, &out)
fmt.Printf("解回: %+v\n", out)
deep := []byte{0x9f} // 0x9f = 不定长数组的开始
for i := 0; i < 40; i++ {
deep = append(deep, 0x9f) // 再嵌 40 层
}
var v any
fmt.Println("深嵌套:", cbor.Unmarshal(deep, &v))
}编码 16 字节: a2 64 6e 61 6d 65 65 66 72 61 6e 6b 61 6e 18 2a
解回: {Name:frank N:42}
深嵌套: cbor: exceeded max nested level 32MaxNestedLevels 调到 4,再解一段 6 层嵌套的数组。本机报的是 exceeded max nested level 4,错误停在你设的那一层。12io.LimitReader 给一段不定长数据的解码套上整体大小上限。限制的是「一共读多少字节」,和嵌套层数、元素个数是两回事。12undefined(0xf7)解到一个 interface 变量里,看它是不是变成 nil;README 列的限制里就写着这一条。12undefined(0xf7)解到 Go 里就是 nil;Go 不支持做 map 键的数据类型会被忽略,解码会继续走完剩下的项,最后才把错误返回;把注册过的 tag 解到 interface 时,解码器给出的是指向注册类型的指针,这一点是 Go 的限制。12literal/extractor.go 里的 cloneRegexp 同时克隆了 Sub 和 Sub0 两个数组,出现 2 的深度次方增长;一个 15 个字符的模式 .(?:ac*d|ac)xy* 能让编译器分配超过 3 GB 内存、编译超过 15 秒。15 修复之后,本机编同一个模式只要 44 微秒。regexp 是一个不优化的单一 NFA 引擎,换来 O(n) 的时间保证;coregex 保留 O(n),在上面叠了 17 种引擎策略。16.*error.*,标准库 138 毫秒对 coregex 169 微秒。16.*关键字.* 这类模式改成从后缀或内部字面量反向找,README 给的区间是 100 到 900 倍。16(?i)、(?m)、(?s) 这些标志则照常支持。16go get 一条命令,需要 Go 1.27 以上。16mkdir coregex-demo && cd coregex-demo && go mod init demo
go get github.com/coregx/coregexpackage main
import (
"fmt"
"regexp"
"strings"
"time"
coregex "github.com/coregx/coregex"
)
func main() {
pat := `\d+\.\d+\.\d+\.\d+`
text := strings.Repeat("lorem ipsum dolor sit amet ", 300000) // 约 7.4 MB,一个数字都没有
std := regexp.MustCompile(pat)
cg := coregex.MustCompile(pat) // 和 regexp 同一套 API
t0 := time.Now()
std.FindAllString(text, -1)
fmt.Println("标准库:", time.Since(t0))
t1 := time.Now()
cg.FindAllString(text, -1)
fmt.Println("coregex:", time.Since(t1))
}标准库: 140.394ms
coregex: 199µsregexp.MustCompile 换成 coregex.MustCompile,先跑一遍现有测试看行为是否一致。这个库的定位就是可以直接替换。16.(?:ac*d|ac)xy* 编一遍并计时。本机是 44 微秒,修复前它是几十秒。15coregex.CompileWithConfig 把 MaxDFAStates 调小,观察大输入上策略被换掉之后耗时怎么变。16| 你想练的主线 | 仓库与本期信号 | 最小条件 | 第一条可观察路径 | 主要限制 |
|---|---|---|---|---|
| 浮点解析与正确舍入 | fast_float · 8.3.1 北京时间 10 月 5 日凌晨发布 | 一个头文件,加 g++ | from_chars 和 strtod 输出同一串位 | 只支持 float 和 double,只认十进制。(README) |
| 二进制编解码与对抗性输入 | fxamacker/cbor · v2.9.6 北京时间 10 月 7 日凌晨发布 | 一条 go get | 40 层嵌套在第 32 层被拒绝 | 默认上限要按场景自己调。(README) |
| 正则引擎与线性时间保证 | coregex · v0.12.26 北京时间 10 月 9 日凌晨发布 | 一条 go get,需要 Go 1.27 | 同一个 IP 模式,7.4 MB 文本上从 140 毫秒降到 199 微秒 | pre-1.0;不支持反向引用;SIMD 预过滤只在 AMD64 上。(README) |
github.com
github.com
github.com
github.com
github.com
github.com
github.com
github.com
pkg.go.dev
rfc-editor.org
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.