Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions devel/0082.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# [0082] bytevector-base64-encode 下沉到 native C++

## 任务相关的代码文件
- src/liii_base64.cpp (新增 encode 主循环)
- goldfish/liii/base64.scm (encode 转发到 native)
- tests/liii/base64/bytevector-base64-encode-test.scm (用例不变,回归用)
- bench/bytevector-base64-encode-perf.scm

## 如何测试
```
xmake config --yes
xmake b goldfish
bin/gf fmt src/liii_base64.cpp
bin/gf fmt goldfish/liii/base64.scm
bin/gf tests/liii/base64/bytevector-base64-encode-test.scm
bin/gf tests/liii/base64/bytevector-base64-decode-test.scm
bin/gf tests/liii/base64/base64-encode-test.scm
bin/gf tests/liii/base64/base64-decode-test.scm
bin/gf bench/bytevector-base64-encode-perf.scm
```

## 2026/06/28 bytevector-base64-encode 下沉到 native C++
### What
将 `(liii base64)` 的 `bytevector-base64-encode` 从纯 Scheme 实现下沉到 native C++,完全保留 [0081](0081.md) 固化的 22 个 check 语义契约,同时获得数百到数千倍的性能提升。

1. 在 `src/liii_base64.cpp` 新增 `f_bytevector_base64_encode`,仿照同文件 decode 的 glue 模式,以 `g_bytevector-base64-encode` 注册到 s7
2. 改写 `goldfish/liii/base64.scm`:`bytevector-base64-encode` 直接转发到 `g_bytevector-base64-encode`,删除原 Scheme 的 `encode` 内部函数、`BYTE2BASE64_BV` 查表常量与 `BASE64_PAD_BYTE`
3. native 实现一次遍历:每 3 字节打包成 `uint32_t` triple,按 6-bit 切分查表输出 4 字节;尾部处理 `rem==1` (`==`) 与 `rem==2` (`=`) 两种填充

### Why
[0080](0080.md) 把 decode 下沉到 native 后,性能瓶颈转移到 encode 路径:large(10KB) 单次约 193ms,是 HTTP、数据 URI、JWT 等 base64 相关场景的下一个热点。下沉 encode 后,`(liii base64)` 的 bytevector 编解码两端都跑在 native 层,string 路径仅余 `utf8->string` / `string->utf8` 转换开销。

### How
- 主循环 `while (i + 2 < in_len)` 一次处理完整 3 字节组,比 decode 的「逐 4 字节查表 + 填充位判断」更简单(encode 无非法输入,输入是任意字节)
- 输出长度 `out_len = 4 * ((in_len + 2) / 3)`,一次性 `s7_make_byte_vector` 分配,无需 decode 那种「先预分配再 memcpy 收紧」的两段式
- 空输入特判 `out_len == 0`,避免 `(0+2)/3` 误生成 4 字节
- 类型校验 `s7_is_byte_vector` 在最外层,与 decode 对称

### 性能对比(debug 构建,单次平均耗时)
| 操作 | 0081 Scheme | 0082 native | 加速比 |
|---|---|---|---|
| bytevector-base64-encode tiny(8B) | ~164µs | ~1.28µs | ~128x |
| bytevector-base64-encode small(100B) | ~1.77ms | ~1.72µs | ~1029x |
| bytevector-base64-encode medium(1KB) | ~18.3ms | ~4.39µs | ~4168x |
| bytevector-base64-encode large(10KB) | ~193ms | ~26µs | ~7423x |

`string-base64-encode` 通过 `utf8->string ∘ bytevector-base64-encode` 复用 native encode,large(10KB) 路径同步受益,瓶颈转移到 utf8 转换层(与 decode 侧的 string 路径表现一致,见 [0080](0080.md))。
49 changes: 1 addition & 48 deletions goldfish/liii/base64.scm
Original file line number Diff line number Diff line change
Expand Up @@ -8,56 +8,9 @@
base64-decode
) ;export
(begin
(define-constant BYTE2BASE64_BV
(string->utf8 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
) ;string->utf8
) ;define-constant

(define-constant BASE64_PAD_BYTE (char->integer #\=))

(define bytevector-base64-encode
(typed-lambda ((bv bytevector?))
(define (encode b1 b2 b3)
(let* ((p1 b1)
(p2 (if b2 b2 0))
(p3 (if b3 b3 0))
(combined (bitwise-ior (ash p1 16) (ash p2 8) p3))
(c1 (bitwise-and (ash combined -18) 63))
(c2 (bitwise-and (ash combined -12) 63))
(c3 (bitwise-and (ash combined -6) 63))
(c4 (bitwise-and combined 63))
) ;
(values (BYTE2BASE64_BV c1)
(BYTE2BASE64_BV c2)
(if b2 (BYTE2BASE64_BV c3) BASE64_PAD_BYTE)
(if b3 (BYTE2BASE64_BV c4) BASE64_PAD_BYTE)
) ;values
) ;let*
) ;define
(let* ((input-N (bytevector-length bv))
(output-N (* 4 (ceiling (/ input-N 3))))
(output (make-bytevector output-N))
) ;
(let loop
((i 0) (j 0))
(when (< i input-N)
(let* ((b1 (bv i))
(b2 (if (< (+ i 1) input-N) (bv (+ i 1)) #f))
(b3 (if (< (+ i 2) input-N) (bv (+ i 2)) #f))
) ;
(receive (r1 r2 r3 r4)
(encode b1 b2 b3)
(bytevector-u8-set! output j r1)
(bytevector-u8-set! output (+ j 1) r2)
(bytevector-u8-set! output (+ j 2) r3)
(bytevector-u8-set! output (+ j 3) r4)
(loop (+ i 3) (+ j 4))
) ;receive
) ;let*
) ;when
) ;let
output
) ;let*
(g_bytevector-base64-encode bv (bytevector-length bv))
) ;typed-lambda
) ;define

Expand Down
63 changes: 63 additions & 0 deletions src/liii_base64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,16 +103,79 @@ f_bytevector_base64_decode (s7_scheme* sc, s7_pointer args) {
return out;
}

static s7_pointer
f_bytevector_base64_encode (s7_scheme* sc, s7_pointer args) {
s7_pointer arg= s7_car (args);

if (!s7_is_byte_vector (arg)) {
return base64_error (sc, "bytevector-base64-encode", "type-error",
"bytevector-base64-encode: input must be bytevector", arg);
}

s7_int in_len= s7_integer (s7_cadr (args));
uint8_t* in = (uint8_t*) s7_byte_vector_elements (arg);

static const uint8_t encode_table[64]= {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

const uint8_t PAD= (uint8_t) '=';

s7_int out_len= (in_len == 0) ? 0 : 4 * ((in_len + 2) / 3);
s7_pointer out = s7_make_byte_vector (sc, out_len, 1, NULL);
uint8_t* out_buf= (uint8_t*) s7_byte_vector_elements (out);

s7_int i= 0;
s7_int j= 0;

while (i + 2 < in_len) {
uint32_t triple= ((uint32_t) in[i] << 16) | ((uint32_t) in[i + 1] << 8) | in[i + 2];
out_buf[j] = encode_table[(triple >> 18) & 0x3F];
out_buf[j + 1] = encode_table[(triple >> 12) & 0x3F];
out_buf[j + 2] = encode_table[(triple >> 6) & 0x3F];
out_buf[j + 3] = encode_table[triple & 0x3F];
i+= 3;
j+= 4;
}

s7_int rem= in_len - i;
if (rem == 1) {
uint32_t triple= (uint32_t) in[i] << 16;
out_buf[j] = encode_table[(triple >> 18) & 0x3F];
out_buf[j + 1] = encode_table[(triple >> 12) & 0x3F];
out_buf[j + 2] = PAD;
out_buf[j + 3] = PAD;
}
else if (rem == 2) {
uint32_t triple= ((uint32_t) in[i] << 16) | ((uint32_t) in[i + 1] << 8);
out_buf[j] = encode_table[(triple >> 18) & 0x3F];
out_buf[j + 1] = encode_table[(triple >> 12) & 0x3F];
out_buf[j + 2] = encode_table[(triple >> 6) & 0x3F];
out_buf[j + 3] = PAD;
}

return out;
}

static void
glue_bytevector_base64_decode (s7_scheme* sc) {
const char* name= "g_bytevector-base64-decode";
const char* desc= "(g_bytevector-base64-decode bv len) => bytevector";
s7_define_function (sc, name, f_bytevector_base64_decode, 2, 0, false, desc);
}

static void
glue_bytevector_base64_encode (s7_scheme* sc) {
const char* name= "g_bytevector-base64-encode";
const char* desc= "(g_bytevector-base64-encode bv len) => bytevector";
s7_define_function (sc, name, f_bytevector_base64_encode, 2, 0, false, desc);
}

void
glue_liii_base64 (s7_scheme* sc) {
glue_bytevector_base64_decode (sc);
glue_bytevector_base64_encode (sc);
}

} // namespace goldfish
Loading