From 7ac53832c54998f7f873b99014dbd90963973b49 Mon Sep 17 00:00:00 2001 From: Da Shen Date: Sat, 27 Jun 2026 00:01:31 +0800 Subject: [PATCH] =?UTF-8?q?[0080]=20bytevector-base64-decode=20=E4=B8=8B?= =?UTF-8?q?=E6=B2=89=E5=88=B0=20native=20C++=20=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devel/0080.md | 57 +++++++++++++++++++ goldfish/liii/base64.scm | 73 +----------------------- src/goldfish.hpp | 2 + src/liii_base64.cpp | 118 +++++++++++++++++++++++++++++++++++++++ xmake.lua | 1 + 5 files changed, 179 insertions(+), 72 deletions(-) create mode 100644 devel/0080.md create mode 100644 src/liii_base64.cpp diff --git a/devel/0080.md b/devel/0080.md new file mode 100644 index 00000000..5f5deded --- /dev/null +++ b/devel/0080.md @@ -0,0 +1,57 @@ +# [0080] 利用 tbox 思路下沉 bytevector-base64-decode 到 C++ 层 + +## 任务相关的代码文件 +- src/liii_base64.cpp (新增) +- src/goldfish.hpp +- xmake.lua +- goldfish/liii/base64.scm +- tests/liii/base64/bytevector-base64-decode-test.scm (用例不变,回归用) +- bench/base64-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-decode-test.scm +bin/gf tests/liii/base64/base64-decode-test.scm +bin/gf tests/liii/base64/base64-encode-test.scm +bin/gf bench/base64-perf.scm +``` + +## 2026/06/26 bytevector-base64-decode 下沉到 native C++ +### What +将 `(liii base64)` 的 `bytevector-base64-decode` 从纯 Scheme 实现下沉到 native C++,完全保留 [0079](0079.md) 修复后的语义契约(36 个 check 全部通过),同时获得数百到数千倍的性能提升。 + +1. 新增 `src/liii_base64.cpp`:仿照 `src/liii_hashlib.cpp` 的 glue 模式,定义 `f_bytevector_base64_decode` 并以 `g_bytevector-base64-decode` 注册到 s7;通过 `s7_error` 抛出 `type-error` / `value-error` +2. 在 `src/goldfish.hpp` 增加 `glue_liii_base64` 声明,并在 `glue_for_community_edition` 中调用 +3. 在 `xmake.lua` 的 goldfish target 中加入 `src/liii_base64.cpp` +4. 改写 `goldfish/liii/base64.scm`:`bytevector-base64-decode` 直接转发到 `g_bytevector-base64-decode`,删除原 Scheme 循环及不再使用的 `BASE64_TO_BYTE_V` 查表 +5. native 实现一次遍历同时完成「长度 4X 校验 + 字符集校验 + `=` 填充位置校验 + 解码」,只在尾部需要时再分配精确长度的 bytevector + +### Why +[0079](0079.md) 把语义收紧到 RFC 4648 后,Scheme 实现成为热点:large(10KB) 单次约 310ms,是后续 base64 相关功能(HTTP、数据 URI、JWT 等)的性能瓶颈。tbox 自带的 `tb_base64_decode` 接口签名(返回 `tb_size_t`,无错误码)与 0079 的严格契约不兼容 —— 它对 `=Zm9`/`Zg=v`/`====`/长度非 4X 等输入不报错,遇到非法字符直接 `return 0` 也无法与合法空输入区分。因此采用方案 B:在 `src/liii_base64.cpp` 中自写 decode 主循环,按 0079 契约严格实现,仍 `#include ` 复用类型风格但不依赖其 base64 实现。 + +### How +- 主循环每 4 字节一组:先检查 c1/c2 不能是 `=`;查表得 v1..v4;只允许 `VVVV` / `VVVP` / `VVPP` 三种合法填充模式(`c3_pad && !c4_pad` 一律报错);查表命中 `0xFF` 一律报错 +- 输出 buffer 按 `(in_len/4)*3` 预分配,循环里同步推进 `out_len`;若最后 `out_len < out_cap`(出现填充),再分配精确长度的 bytevector 并 `memcpy` +- 类型校验在最外层用 `s7_is_byte_vector` 完成;长度通过参数从 Scheme 端传入(s7 未公开 bytevector length API,codebase 既有 `liii_path.cpp` 也是自己存住 length) +- Scheme 端通过 `(bytevector-length bv)` 计算后传给 native,保留 `(bytevector? bv)` 的契约由 native 端兜底 + +### 性能对比(debug 构建,单次平均耗时) +| 操作 | 0079 Scheme | 0080 native | 加速比 | +|---|---|---|---| +| bytevector-base64-decode tiny(8B) | 279µs | ~1.7µs | ~160x | +| bytevector-base64-decode small(100B) | 3.0ms | ~2.4µs | ~1250x | +| bytevector-base64-decode medium(1KB) | 30.7ms | ~7.4µs | ~4150x | +| bytevector-base64-decode large(10KB) | 310ms | ~45µs | ~6900x | +| string-base64-decode large(10KB) | 545ms | ~240ms | ~2.3x | +| base64-decode bytevector(1KB) | ~3ms | ~10µs | ~300x | + +string 路径加速比较小,瓶颈转移到 `utf8->string` / `string->utf8` 转换。bench 整体耗时从约 17s 降至约 9s。 + +## 后续任务(暂不在本 PR 完成) +- bench 迭代次数需要按新性能重新调整(decode 现在太快,统计噪声变大) +- string-base64-decode 的 utf8 转换路径单独优化 +- bytevector-base64-encode 也可考虑下沉到 native,进一步压低 large(10KB) 的 220ms 量级 diff --git a/goldfish/liii/base64.scm b/goldfish/liii/base64.scm index 543f7229..ab72a385 100644 --- a/goldfish/liii/base64.scm +++ b/goldfish/liii/base64.scm @@ -74,79 +74,8 @@ ) ;cond ) ;define - (define-constant BASE64_TO_BYTE_V - (let ((byte2base64-N (bytevector-length BYTE2BASE64_BV))) - (let loop - ((i 0) (v (make-vector 256 -1))) - (if (< i byte2base64-N) - (begin - (vector-set! v (BYTE2BASE64_BV i) i) - (loop (+ i 1) v) - ) ;begin - v - ) ;if - ) ;let - ) ;let - ) ;define-constant - (define (bytevector-base64-decode bv) - (unless (bytevector? bv) - (type-error "input must be bytevector") - ) ;unless - (let ((input-N (bytevector-length bv))) - (unless (zero? (modulo input-N 4)) - (value-error "length of the input bytevector must be 4X") - ) ;unless - (define (decode c1 c2 c3 c4) - (define pad? (lambda (c) (equal? c BASE64_PAD_BYTE))) - (define c3-pad? (pad? c3)) - (define c4-pad? (pad? c4)) - (define b1 (BASE64_TO_BYTE_V c1)) - (define b2 (BASE64_TO_BYTE_V c2)) - (define b3 (BASE64_TO_BYTE_V c3)) - (define b4 (BASE64_TO_BYTE_V c4)) - (unless (and (not (pad? c1)) - (not (pad? c2)) - (not (negative? b1)) - (not (negative? b2)) - (or (and (not c3-pad?) (not c4-pad?) (not (negative? b3)) (not (negative? b4))) - (and (not c3-pad?) c4-pad? (not (negative? b3))) - (and c3-pad? c4-pad?) - ) ;or - ) ;and - (value-error "Invalid base64 input") - ) ;unless - (values (bitwise-ior (ash b1 2) (ash b2 -4)) - (if c3-pad? 0 (bitwise-and (bitwise-ior (ash b2 4) (ash b3 -2)) 255)) - (if c4-pad? 0 (bitwise-and (bitwise-ior (ash b3 6) b4) 255)) - (if c3-pad? 1 (if c4-pad? 2 3)) - ) ;values - ) ;define - (let ((output-N (quotient (* input-N 3) 4))) - (let ((output (make-bytevector output-N))) - (let loop - ((i 0) (j 0)) - (if (< i input-N) - (receive (r1 r2 r3 cnt) - (decode (bv i) (bv (+ i 1)) (bv (+ i 2)) (bv (+ i 3))) - (bytevector-u8-set! output j r1) - (when (>= cnt 2) - (bytevector-u8-set! output (+ j 1) r2) - ) ;when - (when (>= cnt 3) - (bytevector-u8-set! output (+ j 2) r3) - ) ;when - (loop (+ i 4) (+ j cnt)) - ) ;receive - (let ((final (make-bytevector j))) - (vector-copy! final 0 output 0 j) - final - ) ;let - ) ;if - ) ;let - ) ;let - ) ;let - ) ;let + (g_bytevector-base64-decode bv (bytevector-length bv)) ) ;define (define string-base64-decode diff --git a/src/goldfish.hpp b/src/goldfish.hpp index 046ffa27..6598a4fa 100644 --- a/src/goldfish.hpp +++ b/src/goldfish.hpp @@ -108,6 +108,7 @@ static vector find_function_libraries_in_load_path (s7_scheme* sc, const void glue_njson (s7_scheme* sc); void glue_http (s7_scheme* sc); void glue_http_async (s7_scheme* sc); +void glue_liii_base64 (s7_scheme* sc); void glue_liii_hashlib (s7_scheme* sc); void glue_liii_os (s7_scheme* sc); void glue_liii_path (s7_scheme* sc); @@ -699,6 +700,7 @@ glue_for_community_edition (s7_scheme* sc) { glue_liii_datetime (sc); glue_liii_uuid (sc); glue_liii_hashlib (sc); + glue_liii_base64 (sc); glue_njson (sc); glue_http (sc); glue_http_async (sc); diff --git a/src/liii_base64.cpp b/src/liii_base64.cpp new file mode 100644 index 00000000..5ce097cd --- /dev/null +++ b/src/liii_base64.cpp @@ -0,0 +1,118 @@ +// +// Copyright (C) 2024-2026 The Goldfish Scheme Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied See the +// License for the specific language governing permissions and limitations +// under the License. +// + +#include "s7.h" +#include + +namespace goldfish { + +static s7_pointer +base64_error (s7_scheme* sc, const char* caller, const char* kind, const char* msg, s7_pointer arg) { + return s7_error (sc, s7_make_symbol (sc, kind), s7_list (sc, 2, s7_make_string (sc, msg), arg)); +} + +static s7_pointer +f_bytevector_base64_decode (s7_scheme* sc, s7_pointer args) { + s7_pointer arg= s7_car (args); + + if (!s7_is_byte_vector (arg)) { + return base64_error (sc, "bytevector-base64-decode", "type-error", + "bytevector-base64-decode: input must be bytevector", arg); + } + + s7_int in_len= s7_integer (s7_cadr (args)); + uint8_t* in = (uint8_t*) s7_byte_vector_elements (arg); + + if (in_len % 4 != 0) { + return base64_error (sc, "bytevector-base64-decode", "value-error", + "bytevector-base64-decode: length of the input bytevector must be 4X", arg); + } + + static const uint8_t decode_table[256]= { + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, + 255, 255, 255, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255, 255, 0, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 255, 255, 255, 255, 255, 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; + + const uint8_t PAD= (uint8_t) '='; + + s7_int out_cap= (in_len / 4) * 3; + s7_pointer out = s7_make_byte_vector (sc, out_cap, 1, NULL); + uint8_t* out_buf= (uint8_t*) s7_byte_vector_elements (out); + s7_int out_len= 0; + + for (s7_int i= 0; i < in_len; i+= 4) { + uint8_t c1= in[i]; + uint8_t c2= in[i + 1]; + uint8_t c3= in[i + 2]; + uint8_t c4= in[i + 3]; + + bool c3_pad= (c3 == PAD); + bool c4_pad= (c4 == PAD); + + if (c1 == PAD || c2 == PAD) { + return base64_error (sc, "bytevector-base64-decode", "value-error", + "bytevector-base64-decode: Invalid base64 input", arg); + } + + uint8_t v1= decode_table[c1]; + uint8_t v2= decode_table[c2]; + uint8_t v3= c3_pad ? 0 : decode_table[c3]; + uint8_t v4= c4_pad ? 0 : decode_table[c4]; + + if (v1 == 0xFF || v2 == 0xFF || (!c3_pad && v3 == 0xFF) || (!c4_pad && v4 == 0xFF) || (c3_pad && !c4_pad)) { + return base64_error (sc, "bytevector-base64-decode", "value-error", + "bytevector-base64-decode: Invalid base64 input", arg); + } + + out_buf[out_len++]= (uint8_t) ((v1 << 2) | (v2 >> 4)); + if (!c3_pad) { + out_buf[out_len++]= (uint8_t) ((v2 << 4) | (v3 >> 2)); + if (!c4_pad) { + out_buf[out_len++]= (uint8_t) ((v3 << 6) | v4); + } + } + } + + if (out_len != out_cap) { + s7_pointer final_out= s7_make_byte_vector (sc, out_len, 1, NULL); + memcpy (s7_byte_vector_elements (final_out), out_buf, out_len); + return final_out; + } + 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); +} + +void +glue_liii_base64 (s7_scheme* sc) { + glue_bytevector_base64_decode (sc); +} + +} // namespace goldfish diff --git a/xmake.lua b/xmake.lua index fbfd4a5c..d950b3ba 100644 --- a/xmake.lua +++ b/xmake.lua @@ -109,6 +109,7 @@ target ("goldfish") do add_files ("src/liii_os.cpp") add_files ("src/liii_path.cpp") add_files ("src/liii_hashlib.cpp") + add_files ("src/liii_base64.cpp") add_files ("src/s7.c", {languages = "c11"}) add_files ("src/s7_op_names.c", {languages = "c11"}) add_files ("src/s7_scheme_complex.c", {languages = "c11"})