Hi,

This patch adds an RVV-accelerated, bounded NUL-byte scan for
pq_getmsgstring() and pq_getmsgrawstring().


Both functions currently use strlen() and then verify that the terminating
NUL byte is within the message boundary.  On RVV builds, the patch replaces
that scan with a common helper that searches for the NUL byte using RVV
intrinsics.


The vector length is limited to the number of bytes remaining in the
message before every load.  Therefore, the helper does not read beyond the
PostgreSQL message boundary.  If no NUL byte is found within that boundary,
the existing protocol-violation error path is preserved.


The RVV implementation is enabled only when __riscv_vector is defined and
<riscv_vector.h> is available.  Non-RVV builds retain the existing strlen()
path.  The public APIs and the character-set conversion behavior of
pq_getmsgstring() remain unchanged.


The patch is intended for PostgreSQL master.  Full validation was performed
on PostgreSQL commit:


86f7c82cf1023e3599f40f939727791a7090cd44


Correctness and portability results:


- git apply and whitespace checks passed
- RISC-V baseline with GCC 14: 240/240 regression tests passed
- RISC-V patched build with GCC 14: 240/240 passed
- RISC-V patched build with GCC 15: 240/240 passed
- RISC-V patched build with Clang 17: 240/240 passed
- x86_64 fallback with GCC 11.4: 240/240 passed
- x86_64 fallback with Clang 14: 240/240 passed
- guard-page, missing-NUL, length, and unaligned-boundary tests passed
  with GCC 14, GCC 15, and Clang 17


The RISC-V builds used:


CFLAGS="-O2 -g -march=rv64gcv -mabi=lp64d"


Disassembly of the GCC 14 and GCC 15 builds confirmed that the generated
code contains vsetvli, vle8.v, vmseq, and vfirst.m instructions.  Clang 17
generated the same RVV operations, although the target system's objdump
displayed some of them as .insn.


End-to-end performance was measured using GCC 15.1 with a 4 KiB
simple-query workload, one client and one thread.  The test contained 24
baseline/patched pairs, with each member running for 8 seconds.  Execution
order alternated between baseline-first and patched-first.


Results:


- 23 of 24 pairs favored the patched build
- mean TPS improvement: 3.52%
- median TPS improvement: 3.59%
- bootstrap median 95% confidence interval: [3.17%, 3.89%]
- no failed transactions in the 48 measurement runs


A separate microbenchmark covering 55 string-length and alignment
conditions showed a median 3.34x speedup over strlen() and a median 3.97x
speedup over memchr() for the RVV helper.


The performance measurements were performed on one RVV system, and the
end-to-end result is specific to the 4 KiB simple-query, single-client
workload.  It should not be assumed to represent every PostgreSQL workload
or RISC-V implementation.


No new SQL regression test is included because the change does not alter
SQL-visible behavior.  The existing regression suite passed on both the
RVV and scalar fallback paths, while the message-boundary and missing-NUL
cases were covered by the targeted tests described above.  No user-facing
documentation change is needed because this is an internal,
architecture-specific optimization.


Feedback on the bounded scanning approach and the placement of the
RISC-V-specific implementation would be appreciated.


Regards,
Hongyan Wang
From 890872e8dafeab3080022876d6c601d80624deaa Mon Sep 17 00:00:00 2001
From: wanghongyan <[email protected]>
Date: Fri, 18 Sep 2026 14:44:06 +0800
Subject: [PATCH v1] add validated RVV pq_getmsgstring candidate

Co-authored-by: Ni Jincheng <[email protected]>
Co-authored-by: Yuansheng <[email protected]>
---
 src/backend/libpq/pqformat.c | 59 ++++++++++++++++++++++++++++++++++--
 1 file changed, 57 insertions(+), 2 deletions(-)

diff --git a/src/backend/libpq/pqformat.c b/src/backend/libpq/pqformat.c
index 8b41aa4f1cb..4a1dd5a45c2 100644
--- a/src/backend/libpq/pqformat.c
+++ b/src/backend/libpq/pqformat.c
@@ -79,6 +79,13 @@
 #include "port/pg_bswap.h"
 #include "varatt.h"
 
+#if defined(__riscv_vector) && defined(__has_include)
+#if __has_include(<riscv_vector.h>)
+#include <riscv_vector.h>
+#define USE_RISCV_VECTOR_NUL_SCAN
+#endif
+#endif
+
 
 /* --------------------------------
  *             pq_beginmessage         - initialize for sending a message
@@ -567,6 +574,10 @@ pq_getmsgtext(StringInfo msg, int rawbytes, int *nbytes)
        return p;
 }
 
+#ifdef USE_RISCV_VECTOR_NUL_SCAN
+static int pq_find_nul_rvv(const char *str, int maxlen);
+#endif
+
 /* --------------------------------
  *             pq_getmsgstring - get a null-terminated text string (with 
conversion)
  *
@@ -587,8 +598,12 @@ pq_getmsgstring(StringInfo msg)
         * have a trailing null byte.  But check we found a null inside the
         * message.
         */
+#ifdef USE_RISCV_VECTOR_NUL_SCAN
+       slen = pq_find_nul_rvv(str, msg->len - msg->cursor);
+#else
        slen = strlen(str);
-       if (msg->cursor + slen >= msg->len)
+#endif
+       if (slen < 0 || msg->cursor + slen >= msg->len)
                ereport(ERROR,
                                (errcode(ERRCODE_PROTOCOL_VIOLATION),
                                 errmsg("invalid string in message")));
@@ -597,6 +612,42 @@ pq_getmsgstring(StringInfo msg)
        return pg_client_to_server(str, slen);
 }
 
+#ifdef USE_RISCV_VECTOR_NUL_SCAN
+/*
+ * Find a null byte without reading beyond the message boundary.  Keeping the
+ * vector length bounded by maxlen avoids the object and page-boundary
+ * over-read that an unbounded vector strlen implementation could perform.
+ */
+static int
+pq_find_nul_rvv(const char *str, int maxlen)
+{
+       size_t          offset = 0;
+       size_t          remaining;
+
+       if (maxlen <= 0)
+               return -1;
+       remaining = maxlen;
+
+       while (remaining > 0)
+       {
+               size_t          vl = __riscv_vsetvl_e8m8(remaining);
+               vuint8m8_t      bytes;
+               vbool1_t        zeroes;
+               long            position;
+
+               bytes = __riscv_vle8_v_u8m8((const uint8_t *) str + offset, vl);
+               zeroes = __riscv_vmseq_vx_u8m8_b1(bytes, 0, vl);
+               position = __riscv_vfirst_m_b1(zeroes, vl);
+               if (position >= 0)
+                       return (int) (offset + (size_t) position);
+               offset += vl;
+               remaining -= vl;
+       }
+
+       return -1;
+}
+#endif
+
 /* --------------------------------
  *             pq_getmsgrawstring - get a null-terminated text string - NO 
conversion
  *
@@ -616,8 +667,12 @@ pq_getmsgrawstring(StringInfo msg)
         * have a trailing null byte.  But check we found a null inside the
         * message.
         */
+#ifdef USE_RISCV_VECTOR_NUL_SCAN
+       slen = pq_find_nul_rvv(str, msg->len - msg->cursor);
+#else
        slen = strlen(str);
-       if (msg->cursor + slen >= msg->len)
+#endif
+       if (slen < 0 || msg->cursor + slen >= msg->len)
                ereport(ERROR,
                                (errcode(ERRCODE_PROTOCOL_VIOLATION),
                                 errmsg("invalid string in message")));
-- 
2.43.0

Reply via email to