Skip to content

Commit b7bb3a2

Browse files
authored
[0082] bytevector-base64-encode 下沉到 native C++ 实现 (#880)
1 parent 6c1011a commit b7bb3a2

3 files changed

Lines changed: 111 additions & 48 deletions

File tree

devel/0082.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# [0082] bytevector-base64-encode 下沉到 native C++
2+
3+
## 任务相关的代码文件
4+
- src/liii_base64.cpp (新增 encode 主循环)
5+
- goldfish/liii/base64.scm (encode 转发到 native)
6+
- tests/liii/base64/bytevector-base64-encode-test.scm (用例不变,回归用)
7+
- bench/bytevector-base64-encode-perf.scm
8+
9+
## 如何测试
10+
```
11+
xmake config --yes
12+
xmake b goldfish
13+
bin/gf fmt src/liii_base64.cpp
14+
bin/gf fmt goldfish/liii/base64.scm
15+
bin/gf tests/liii/base64/bytevector-base64-encode-test.scm
16+
bin/gf tests/liii/base64/bytevector-base64-decode-test.scm
17+
bin/gf tests/liii/base64/base64-encode-test.scm
18+
bin/gf tests/liii/base64/base64-decode-test.scm
19+
bin/gf bench/bytevector-base64-encode-perf.scm
20+
```
21+
22+
## 2026/06/28 bytevector-base64-encode 下沉到 native C++
23+
### What
24+
`(liii base64)``bytevector-base64-encode` 从纯 Scheme 实现下沉到 native C++,完全保留 [0081](0081.md) 固化的 22 个 check 语义契约,同时获得数百到数千倍的性能提升。
25+
26+
1.`src/liii_base64.cpp` 新增 `f_bytevector_base64_encode`,仿照同文件 decode 的 glue 模式,以 `g_bytevector-base64-encode` 注册到 s7
27+
2. 改写 `goldfish/liii/base64.scm``bytevector-base64-encode` 直接转发到 `g_bytevector-base64-encode`,删除原 Scheme 的 `encode` 内部函数、`BYTE2BASE64_BV` 查表常量与 `BASE64_PAD_BYTE`
28+
3. native 实现一次遍历:每 3 字节打包成 `uint32_t` triple,按 6-bit 切分查表输出 4 字节;尾部处理 `rem==1` (`==`) 与 `rem==2` (`=`) 两种填充
29+
30+
### Why
31+
[0080](0080.md) 把 decode 下沉到 native 后,性能瓶颈转移到 encode 路径:large(10KB) 单次约 193ms,是 HTTP、数据 URI、JWT 等 base64 相关场景的下一个热点。下沉 encode 后,`(liii base64)` 的 bytevector 编解码两端都跑在 native 层,string 路径仅余 `utf8->string` / `string->utf8` 转换开销。
32+
33+
### How
34+
- 主循环 `while (i + 2 < in_len)` 一次处理完整 3 字节组,比 decode 的「逐 4 字节查表 + 填充位判断」更简单(encode 无非法输入,输入是任意字节)
35+
- 输出长度 `out_len = 4 * ((in_len + 2) / 3)`,一次性 `s7_make_byte_vector` 分配,无需 decode 那种「先预分配再 memcpy 收紧」的两段式
36+
- 空输入特判 `out_len == 0`,避免 `(0+2)/3` 误生成 4 字节
37+
- 类型校验 `s7_is_byte_vector` 在最外层,与 decode 对称
38+
39+
### 性能对比(debug 构建,单次平均耗时)
40+
| 操作 | 0081 Scheme | 0082 native | 加速比 |
41+
|---|---|---|---|
42+
| bytevector-base64-encode tiny(8B) | ~164µs | ~1.28µs | ~128x |
43+
| bytevector-base64-encode small(100B) | ~1.77ms | ~1.72µs | ~1029x |
44+
| bytevector-base64-encode medium(1KB) | ~18.3ms | ~4.39µs | ~4168x |
45+
| bytevector-base64-encode large(10KB) | ~193ms | ~26µs | ~7423x |
46+
47+
`string-base64-encode` 通过 `utf8->string ∘ bytevector-base64-encode` 复用 native encode,large(10KB) 路径同步受益,瓶颈转移到 utf8 转换层(与 decode 侧的 string 路径表现一致,见 [0080](0080.md))。

goldfish/liii/base64.scm

Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,56 +8,9 @@
88
base64-decode
99
) ;export
1010
(begin
11-
(define-constant BYTE2BASE64_BV
12-
(string->utf8 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
13-
) ;string->utf8
14-
) ;define-constant
15-
16-
(define-constant BASE64_PAD_BYTE (char->integer #\=))
17-
1811
(define bytevector-base64-encode
1912
(typed-lambda ((bv bytevector?))
20-
(define (encode b1 b2 b3)
21-
(let* ((p1 b1)
22-
(p2 (if b2 b2 0))
23-
(p3 (if b3 b3 0))
24-
(combined (bitwise-ior (ash p1 16) (ash p2 8) p3))
25-
(c1 (bitwise-and (ash combined -18) 63))
26-
(c2 (bitwise-and (ash combined -12) 63))
27-
(c3 (bitwise-and (ash combined -6) 63))
28-
(c4 (bitwise-and combined 63))
29-
) ;
30-
(values (BYTE2BASE64_BV c1)
31-
(BYTE2BASE64_BV c2)
32-
(if b2 (BYTE2BASE64_BV c3) BASE64_PAD_BYTE)
33-
(if b3 (BYTE2BASE64_BV c4) BASE64_PAD_BYTE)
34-
) ;values
35-
) ;let*
36-
) ;define
37-
(let* ((input-N (bytevector-length bv))
38-
(output-N (* 4 (ceiling (/ input-N 3))))
39-
(output (make-bytevector output-N))
40-
) ;
41-
(let loop
42-
((i 0) (j 0))
43-
(when (< i input-N)
44-
(let* ((b1 (bv i))
45-
(b2 (if (< (+ i 1) input-N) (bv (+ i 1)) #f))
46-
(b3 (if (< (+ i 2) input-N) (bv (+ i 2)) #f))
47-
) ;
48-
(receive (r1 r2 r3 r4)
49-
(encode b1 b2 b3)
50-
(bytevector-u8-set! output j r1)
51-
(bytevector-u8-set! output (+ j 1) r2)
52-
(bytevector-u8-set! output (+ j 2) r3)
53-
(bytevector-u8-set! output (+ j 3) r4)
54-
(loop (+ i 3) (+ j 4))
55-
) ;receive
56-
) ;let*
57-
) ;when
58-
) ;let
59-
output
60-
) ;let*
13+
(g_bytevector-base64-encode bv (bytevector-length bv))
6114
) ;typed-lambda
6215
) ;define
6316

src/liii_base64.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,16 +103,79 @@ f_bytevector_base64_decode (s7_scheme* sc, s7_pointer args) {
103103
return out;
104104
}
105105

106+
static s7_pointer
107+
f_bytevector_base64_encode (s7_scheme* sc, s7_pointer args) {
108+
s7_pointer arg= s7_car (args);
109+
110+
if (!s7_is_byte_vector (arg)) {
111+
return base64_error (sc, "bytevector-base64-encode", "type-error",
112+
"bytevector-base64-encode: input must be bytevector", arg);
113+
}
114+
115+
s7_int in_len= s7_integer (s7_cadr (args));
116+
uint8_t* in = (uint8_t*) s7_byte_vector_elements (arg);
117+
118+
static const uint8_t encode_table[64]= {
119+
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
120+
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
121+
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
122+
123+
const uint8_t PAD= (uint8_t) '=';
124+
125+
s7_int out_len= (in_len == 0) ? 0 : 4 * ((in_len + 2) / 3);
126+
s7_pointer out = s7_make_byte_vector (sc, out_len, 1, NULL);
127+
uint8_t* out_buf= (uint8_t*) s7_byte_vector_elements (out);
128+
129+
s7_int i= 0;
130+
s7_int j= 0;
131+
132+
while (i + 2 < in_len) {
133+
uint32_t triple= ((uint32_t) in[i] << 16) | ((uint32_t) in[i + 1] << 8) | in[i + 2];
134+
out_buf[j] = encode_table[(triple >> 18) & 0x3F];
135+
out_buf[j + 1] = encode_table[(triple >> 12) & 0x3F];
136+
out_buf[j + 2] = encode_table[(triple >> 6) & 0x3F];
137+
out_buf[j + 3] = encode_table[triple & 0x3F];
138+
i+= 3;
139+
j+= 4;
140+
}
141+
142+
s7_int rem= in_len - i;
143+
if (rem == 1) {
144+
uint32_t triple= (uint32_t) in[i] << 16;
145+
out_buf[j] = encode_table[(triple >> 18) & 0x3F];
146+
out_buf[j + 1] = encode_table[(triple >> 12) & 0x3F];
147+
out_buf[j + 2] = PAD;
148+
out_buf[j + 3] = PAD;
149+
}
150+
else if (rem == 2) {
151+
uint32_t triple= ((uint32_t) in[i] << 16) | ((uint32_t) in[i + 1] << 8);
152+
out_buf[j] = encode_table[(triple >> 18) & 0x3F];
153+
out_buf[j + 1] = encode_table[(triple >> 12) & 0x3F];
154+
out_buf[j + 2] = encode_table[(triple >> 6) & 0x3F];
155+
out_buf[j + 3] = PAD;
156+
}
157+
158+
return out;
159+
}
160+
106161
static void
107162
glue_bytevector_base64_decode (s7_scheme* sc) {
108163
const char* name= "g_bytevector-base64-decode";
109164
const char* desc= "(g_bytevector-base64-decode bv len) => bytevector";
110165
s7_define_function (sc, name, f_bytevector_base64_decode, 2, 0, false, desc);
111166
}
112167

168+
static void
169+
glue_bytevector_base64_encode (s7_scheme* sc) {
170+
const char* name= "g_bytevector-base64-encode";
171+
const char* desc= "(g_bytevector-base64-encode bv len) => bytevector";
172+
s7_define_function (sc, name, f_bytevector_base64_encode, 2, 0, false, desc);
173+
}
174+
113175
void
114176
glue_liii_base64 (s7_scheme* sc) {
115177
glue_bytevector_base64_decode (sc);
178+
glue_bytevector_base64_encode (sc);
116179
}
117180

118181
} // namespace goldfish

0 commit comments

Comments
 (0)