diff --git a/devel/0082.md b/devel/0082.md new file mode 100644 index 00000000..b4e07109 --- /dev/null +++ b/devel/0082.md @@ -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))。 diff --git a/goldfish/liii/base64.scm b/goldfish/liii/base64.scm index ab72a385..9cc5daba 100644 --- a/goldfish/liii/base64.scm +++ b/goldfish/liii/base64.scm @@ -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 diff --git a/src/liii_base64.cpp b/src/liii_base64.cpp index 5ce097cd..0fa745f6 100644 --- a/src/liii_base64.cpp +++ b/src/liii_base64.cpp @@ -103,6 +103,61 @@ 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"; @@ -110,9 +165,17 @@ glue_bytevector_base64_decode (s7_scheme* sc) { 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