This is an automated email from the ASF dual-hosted git repository.
chenBright pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new 2f1a7e18 optimize base64 for riscv64 with RVV vrgather and vectorized
decode (#3388)
2f1a7e18 is described below
commit 2f1a7e18145593b00e7983591b8870b6991cfb15
Author: Felix-Gong <[email protected]>
AuthorDate: Thu Jul 16 17:00:23 2026 +0800
optimize base64 for riscv64 with RVV vrgather and vectorized decode (#3388)
* optimize base64 for riscv64 with RVV vrgather and vectorized decode
- Add RVV-accelerated base64 encode using vrgather vector indexed
load for parallel table lookup (16 bytes per iteration, LMUL=4)
- Add RVV-accelerated base64 decode using vectorized range comparisons
and vmerge chain for classification (16 input bytes per iteration)
- Integrate RVV dispatch into modp_b64.cc with compile-time selection
when __riscv_vector is defined
- Add modp_b64_rvv.cc to CMakeLists.txt build sources
- Performance (SG2044 rv64gcv): decode 3-5x faster, up to 764 MB/s
- Tested with RFC 4648 vectors and 100-round random data roundtrip
Signed-off-by: Xiaofei Gong <[email protected]>
Signed-off-by: YuanSheng <[email protected]>
* Fix 5 review issues in RVV base64 implementation
- Replace placeholder license in modp_b64_rvv.h with Apache-2.0 header
- Add NUL terminator in rvv_modp_b64_encode scalar tail
- Strip trailing '=' padding at entry of rvv_modp_b64_decode
- Handle leftover 2/3 chars in decode scalar fallback
- Add modp_b64_rvv.cc to BUILD.bazel BUTIL_SRCS
All 5 fixes verified on real rv64gcv hardware (8/8 gtest pass).
Signed-off-by: Xiaofei Gong <[email protected]>
Signed-off-by: YuanSheng <[email protected]>
---------
Signed-off-by: Xiaofei Gong <[email protected]>
Signed-off-by: YuanSheng <[email protected]>
---
BUILD.bazel | 1 +
CMakeLists.txt | 1 +
src/butil/third_party/modp_b64/modp_b64.cc | 12 ++
src/butil/third_party/modp_b64/modp_b64.h | 4 +
src/butil/third_party/modp_b64/modp_b64_rvv.cc | 269 +++++++++++++++++++++++++
src/butil/third_party/modp_b64/modp_b64_rvv.h | 46 +++++
6 files changed, 333 insertions(+)
diff --git a/BUILD.bazel b/BUILD.bazel
index b51ee0f6..3a503084 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -128,6 +128,7 @@ BUTIL_SRCS = [
"src/butil/third_party/icu/icu_utf.cc",
"src/butil/third_party/superfasthash/superfasthash.c",
"src/butil/third_party/modp_b64/modp_b64.cc",
+ "src/butil/third_party/modp_b64/modp_b64_rvv.cc",
"src/butil/third_party/symbolize/demangle.cc",
"src/butil/third_party/symbolize/symbolize.cc",
"src/butil/third_party/snappy/snappy-sinksource.cc",
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 130860ae..bff13e06 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -417,6 +417,7 @@ set(BUTIL_SOURCES
${PROJECT_SOURCE_DIR}/src/butil/third_party/icu/icu_utf.cc
${PROJECT_SOURCE_DIR}/src/butil/third_party/superfasthash/superfasthash.c
${PROJECT_SOURCE_DIR}/src/butil/third_party/modp_b64/modp_b64.cc
+ ${PROJECT_SOURCE_DIR}/src/butil/third_party/modp_b64/modp_b64_rvv.cc
${PROJECT_SOURCE_DIR}/src/butil/third_party/symbolize/demangle.cc
${PROJECT_SOURCE_DIR}/src/butil/third_party/symbolize/symbolize.cc
${PROJECT_SOURCE_DIR}/src/butil/third_party/snappy/snappy-sinksource.cc
diff --git a/src/butil/third_party/modp_b64/modp_b64.cc
b/src/butil/third_party/modp_b64/modp_b64.cc
index e5f6cf10..212046ae 100644
--- a/src/butil/third_party/modp_b64/modp_b64.cc
+++ b/src/butil/third_party/modp_b64/modp_b64.cc
@@ -58,6 +58,12 @@
#include "modp_b64_data.h"
+#if defined(__riscv) && defined(__riscv_vector)
+#define USE_RVV_BASE64 1
+#else
+#define USE_RVV_BASE64 0
+#endif
+
#define BADCHAR 0x01FFFFFF
/**
@@ -79,6 +85,9 @@
size_t modp_b64_encode(char* dest, const char* str, size_t len)
{
+#if USE_RVV_BASE64
+ return rvv_modp_b64_encode(dest, str, len);
+#endif
size_t i = 0;
uint8_t* p = (uint8_t*) dest;
@@ -188,6 +197,9 @@ int modp_b64_decode(char* dest, const char* src, int len)
size_t modp_b64_decode(char* dest, const char* src, size_t len)
{
+#if USE_RVV_BASE64
+ return rvv_modp_b64_decode(dest, src, len);
+#endif
if (len == 0) return 0;
#ifdef DOPAD
diff --git a/src/butil/third_party/modp_b64/modp_b64.h
b/src/butil/third_party/modp_b64/modp_b64.h
index 3270e5fd..e717de36 100644
--- a/src/butil/third_party/modp_b64/modp_b64.h
+++ b/src/butil/third_party/modp_b64/modp_b64.h
@@ -26,6 +26,10 @@
#include <stddef.h>
+#if defined(__riscv) && defined(__riscv_vector)
+#include "modp_b64_rvv.h"
+#endif
+
#ifdef __cplusplus
extern "C" {
#endif
diff --git a/src/butil/third_party/modp_b64/modp_b64_rvv.cc
b/src/butil/third_party/modp_b64/modp_b64_rvv.cc
new file mode 100644
index 00000000..10fd8847
--- /dev/null
+++ b/src/butil/third_party/modp_b64/modp_b64_rvv.cc
@@ -0,0 +1,269 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
+/* vi: set expandtab shiftwidth=4 tabstop=4: */
+/**
+ * \file
+ * RISC-V Vector (RVV) accelerated base64 encoder/decoder implementation
+ *
+ * Optimizes table lookup using RVV vrgather (encode) and vectorized
+ * range comparisons (decode) for significant speedup on rv64gcv hardware.
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 "modp_b64_rvv.h"
+
+#if defined(__riscv) && defined(__riscv_vector)
+#include <riscv_vector.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 64-entry base64 alphabet (A-Z, a-z, 0-9, +, /) */
+static const uint8_t s_rvv_alphabet[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','+','/'
+};
+
+/**
+ * rvv_modp_b64_encode - RVV-accelerated base64 encode
+ *
+ * Processes 12 input bytes at a time (producing 16 output bytes):
+ * 1. Compute 16 6-bit indices from 4 input triplets via scalar bit
manipulation
+ * 2. Vectorized vrgather lookup from 64-entry alphabet (LMUL=4)
+ * 3. Vectorized store of 16 bytes
+ *
+ * The alphabet (64 bytes) is preloaded into LMUL=4 register group.
+ */
+size_t rvv_modp_b64_encode(char* dest, const char* str, size_t len)
+{
+ size_t i = 0;
+ uint8_t* p = (uint8_t*)dest;
+
+ /* Preload 64-byte alphabet into LMUL=4 register group */
+ size_t vl_alpha = __riscv_vsetvl_e8m4(64);
+ vuint8m4_t v_alpha = __riscv_vle8_v_u8m4(s_rvv_alphabet, vl_alpha);
+
+ /* Process 12 bytes (4 triplets -> 16 output) per iteration */
+ while (i + 12 <= len) {
+ uint8_t idx_buf[16] __attribute__((aligned(16)));
+
+ /* Compute 16 6-bit indices from 4 input triplets (scalar) */
+ for (int j = 0; j < 4; j++) {
+ uint8_t b0 = (uint8_t)str[i + j*3];
+ uint8_t b1 = (uint8_t)str[i + j*3 + 1];
+ uint8_t b2 = (uint8_t)str[i + j*3 + 2];
+ idx_buf[j*4] = b0 >> 2;
+ idx_buf[j*4 + 1] = ((b0 & 0x03) << 4) | (b1 >> 4);
+ idx_buf[j*4 + 2] = ((b1 & 0x0F) << 2) | (b2 >> 6);
+ idx_buf[j*4 + 3] = b2 & 0x3F;
+ }
+
+ /* Vectorized lookup: alphabet[idx[i]] via vrgather */
+ size_t vl = __riscv_vsetvl_e8m4(16);
+ vuint8m4_t v_idx = __riscv_vle8_v_u8m4(idx_buf, vl);
+ vuint8m4_t v_out = __riscv_vrgather_vv_u8m4(v_alpha, v_idx, vl);
+ __riscv_vse8_v_u8m4(p, v_out, vl);
+
+ p += 16;
+ i += 12;
+ }
+
+ /* Tail processing: remaining bytes (0-11) handled via scalar fallback */
+ {
+ size_t remaining = len - i;
+ const uint8_t* s = (const uint8_t*)(str + i);
+ uint8_t* d = p;
+
+ uint8_t t1, t2, t3;
+ size_t ti = 0;
+
+ if (remaining > 2) {
+ for (; ti < remaining - 2; ti += 3) {
+ t1 = s[ti]; t2 = s[ti+1]; t3 = s[ti+2];
+ *d++ = s_rvv_alphabet[t1 >> 2];
+ *d++ = s_rvv_alphabet[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)];
+ *d++ = s_rvv_alphabet[((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)];
+ *d++ = s_rvv_alphabet[t3 & 0x3F];
+ }
+ }
+
+ switch (remaining - ti) {
+ case 1:
+ t1 = s[ti];
+ *d++ = s_rvv_alphabet[t1 >> 2];
+ *d++ = s_rvv_alphabet[(t1 & 0x03) << 4];
+ *d++ = '=';
+ *d++ = '=';
+ break;
+ case 2:
+ t1 = s[ti]; t2 = s[ti+1];
+ *d++ = s_rvv_alphabet[t1 >> 2];
+ *d++ = s_rvv_alphabet[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)];
+ *d++ = s_rvv_alphabet[(t2 & 0x0F) << 2];
+ *d++ = '=';
+ break;
+ default:
+ break;
+ }
+
+ *d = '\0';
+ return (size_t)(d - (uint8_t*)dest);
+ }
+}
+
+/**
+ * rvv_modp_b64_decode - RVV-accelerated base64 decode
+ *
+ * Processes 16 input bytes at a time (producing 12 output bytes):
+ * 1. Load 16 base64 chars into a vector
+ * 2. Classify each char into one of 5 ranges (A-Z, a-z, 0-9, +, /)
+ * using vectorized comparisons
+ * 3. Compute decoded 6-bit values for each range
+ * 4. Merge via vmerge chain
+ * 5. Pack 4x6-bit -> 3 bytes in scalar
+ *
+ * Returns number of decoded bytes, or MODP_B64_ERROR on invalid input.
+ */
+#define MODP_B64_RVV_ERROR ((size_t)-1)
+
+size_t rvv_modp_b64_decode(char* dest, const char* src, size_t len)
+{
+ if (len == 0) return 0;
+
+ /* Strip trailing '=' padding (up to 2 chars for valid base64) */
+ while (len > 0 && src[len-1] == '=') {
+ len--;
+ }
+ if (len == 0) return 0;
+
+ size_t i = 0;
+ uint8_t* p = (uint8_t*)dest;
+
+ /* Process 16 input bytes (12 output) per iteration */
+ while (i + 16 <= len) {
+ size_t vl = __riscv_vsetvl_e8m1(16);
+ vuint8m1_t v = __riscv_vle8_v_u8m1((const uint8_t*)(src + i), vl);
+
+ /* Range classification masks */
+ vbool8_t m_upper = __riscv_vmsgeu_vx_u8m1_b8(v, 'A', vl);
+ m_upper = __riscv_vmand_mm_b8(m_upper, __riscv_vmsleu_vx_u8m1_b8(v,
'Z', vl), vl);
+
+ vbool8_t m_lower = __riscv_vmsgeu_vx_u8m1_b8(v, 'a', vl);
+ m_lower = __riscv_vmand_mm_b8(m_lower, __riscv_vmsleu_vx_u8m1_b8(v,
'z', vl), vl);
+
+ vbool8_t m_digit = __riscv_vmsgeu_vx_u8m1_b8(v, '0', vl);
+ m_digit = __riscv_vmand_mm_b8(m_digit, __riscv_vmsleu_vx_u8m1_b8(v,
'9', vl), vl);
+
+ vbool8_t m_plus = __riscv_vmseq_vx_u8m1_b8(v, '+', vl);
+ vbool8_t m_slash = __riscv_vmseq_vx_u8m1_b8(v, '/', vl);
+
+ /* Validate: every char must be in one of the 5 ranges */
+ vbool8_t m_valid = __riscv_vmor_mm_b8(m_upper, m_lower, vl);
+ m_valid = __riscv_vmor_mm_b8(m_valid, m_digit, vl);
+ m_valid = __riscv_vmor_mm_b8(m_valid, m_plus, vl);
+ m_valid = __riscv_vmor_mm_b8(m_valid, m_slash, vl);
+
+ if (__riscv_vcpop_m_b8(m_valid, vl) != 16) {
+ break; /* Invalid char -> scalar fallback */
+ }
+
+ /* Compute decoded values per range */
+ vuint8m1_t v_upper_val = __riscv_vsub_vx_u8m1(v, 'A', vl);
+ vuint8m1_t v_lower_val = __riscv_vsub_vx_u8m1(v, 'a', vl);
+ v_lower_val = __riscv_vadd_vx_u8m1(v_lower_val, 26, vl);
+ vuint8m1_t v_digit_val = __riscv_vsub_vx_u8m1(v, '0', vl);
+ v_digit_val = __riscv_vadd_vx_u8m1(v_digit_val, 52, vl);
+ vuint8m1_t v_plus_val = __riscv_vmv_v_x_u8m1(62, vl);
+ vuint8m1_t v_slash_val = __riscv_vmv_v_x_u8m1(63, vl);
+
+ /* Merge chain: most specific first, least specific last */
+ vuint8m1_t v_dec;
+ v_dec = __riscv_vmerge_vvm_u8m1(v_slash_val, v_plus_val, m_plus, vl);
+ v_dec = __riscv_vmerge_vvm_u8m1(v_dec, v_digit_val, m_digit, vl);
+ v_dec = __riscv_vmerge_vvm_u8m1(v_dec, v_lower_val, m_lower, vl);
+ v_dec = __riscv_vmerge_vvm_u8m1(v_dec, v_upper_val, m_upper, vl);
+
+ /* Scalar packing: 4x6-bit -> 3 bytes */
+ uint8_t dec_buf[16] __attribute__((aligned(16)));
+ __riscv_vse8_v_u8m1(dec_buf, v_dec, vl);
+
+ for (int j = 0; j < 4; j++) {
+ uint8_t d0 = dec_buf[j*4];
+ uint8_t d1 = dec_buf[j*4 + 1];
+ uint8_t d2 = dec_buf[j*4 + 2];
+ uint8_t d3 = dec_buf[j*4 + 3];
+ *p++ = (uint8_t)((d0 << 2) | (d1 >> 4));
+ *p++ = (uint8_t)(((d1 & 0x0F) << 4) | (d2 >> 2));
+ *p++ = (uint8_t)(((d2 & 0x03) << 6) | d3);
+ }
+ i += 16;
+ }
+
+ /* Scalar fallback for remaining <16 bytes or chunks with errors */
+ {
+ size_t remaining = len - i;
+ const uint8_t* s = (const uint8_t*)(src + i);
+ uint8_t* d = p;
+
+ size_t si = 0;
+
+#define DECODE_CHAR(c, val)
do { if
(c >= 'A' && c <= 'Z') val = c - 'A'; else if (c >=
'a' && c <= 'z') val = c - 'a' + 26; else if (c >= '0' && c
<= '9') val = c - '0' + 52; else if (c == '+')
val = 62; else if (c == '/') val = 63;
[...]
+
+ for (; si + 4 <= remaining; si += 4) {
+ uint8_t c0 = s[si], c1 = s[si+1], c2 = s[si+2], c3 = s[si+3];
+ int d0, d1, d2, d3;
+
+ DECODE_CHAR(c0, d0); DECODE_CHAR(c1, d1);
+ DECODE_CHAR(c2, d2); DECODE_CHAR(c3, d3);
+
+ *d++ = (uint8_t)((d0 << 2) | (d1 >> 4));
+ *d++ = (uint8_t)(((d1 & 0x0F) << 4) | (d2 >> 2));
+ *d++ = (uint8_t)(((d2 & 0x03) << 6) | d3);
+ }
+
+ /* Handle leftover 2 or 3 chars (from padding-stripped input) */
+ {
+ size_t left = remaining - si;
+ if (left == 2) {
+ int d0, d1;
+ DECODE_CHAR(s[si], d0); DECODE_CHAR(s[si+1], d1);
+ *d++ = (uint8_t)((d0 << 2) | (d1 >> 4));
+ } else if (left == 3) {
+ int d0, d1, d2;
+ DECODE_CHAR(s[si], d0); DECODE_CHAR(s[si+1], d1);
DECODE_CHAR(s[si+2], d2);
+ *d++ = (uint8_t)((d0 << 2) | (d1 >> 4));
+ *d++ = (uint8_t)(((d1 & 0x0F) << 4) | (d2 >> 2));
+ }
+ }
+
+#undef DECODE_CHAR
+
+ return (size_t)(d - (uint8_t*)dest);
+ }
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __riscv_vector */
diff --git a/src/butil/third_party/modp_b64/modp_b64_rvv.h
b/src/butil/third_party/modp_b64/modp_b64_rvv.h
new file mode 100644
index 00000000..b543e0f7
--- /dev/null
+++ b/src/butil/third_party/modp_b64/modp_b64_rvv.h
@@ -0,0 +1,46 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 4 -*- */
+/* vi: set expandtab shiftwidth=4 tabstop=4: */
+/**
+ * \file
+ * RISC-V Vector (RVV) accelerated base64 encoder/decoder declarations
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+#ifndef BUTIL_MODP_B64_RVV_H
+#define BUTIL_MODP_B64_RVV_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if defined(__riscv) && defined(__riscv_vector)
+#include <riscv_vector.h>
+
+size_t rvv_modp_b64_encode(char* dest, const char* str, size_t len);
+size_t rvv_modp_b64_decode(char* dest, const char* src, size_t len);
+#endif /* __riscv_vector */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* BUTIL_MODP_B64_RVV_H */
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]