v18-0004-Check-ascii-8-bytes-at-a-time-with-bitwise-opera.patch
application/octet-stream
Filename: v18-0004-Check-ascii-8-bytes-at-a-time-with-bitwise-opera.patch
Type: application/octet-stream
Part: 2
Message:
Re: speed up verifying UTF-8
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v18-0004
Subject: Check ascii 8-bytes at a time with bitwise operations
| File | + | − |
|---|---|---|
| src/common/wchar.c | 46 | 7 |
| src/test/regress/expected/conversion.out | 1 | 1 |
| src/test/regress/sql/conversion.sql | 1 | 1 |
From 6360898811851d0b5484e6aab7219cd7379c20e1 Mon Sep 17 00:00:00 2001
From: John Naylor <john.naylor@2ndquadrant.com>
Date: Sun, 18 Jul 2021 19:11:25 -0400
Subject: [PATCH v18 4/6] Check ascii 8-bytes at a time with bitwise operations
---
src/common/wchar.c | 53 ++++++++++++++++++++----
src/test/regress/expected/conversion.out | 2 +-
src/test/regress/sql/conversion.sql | 2 +-
3 files changed, 48 insertions(+), 9 deletions(-)
diff --git a/src/common/wchar.c b/src/common/wchar.c
index 4ea352bcf1..bba54912f9 100644
--- a/src/common/wchar.c
+++ b/src/common/wchar.c
@@ -1932,24 +1932,63 @@ utf8_advance(const unsigned char *s)
return l;
}
+/* Verify a chunk of bytes for valid ASCII including a zero-byte check. */
+static inline int
+check_ascii(const unsigned char *s, int len)
+{
+ uint64 chunk,
+ highbits_set,
+ highbit_carry;
+
+ if (len >= sizeof(uint64))
+ {
+ memcpy(&chunk, s, sizeof(uint64));
+
+ /* Check if any bytes in this chunk have the high bit set. */
+ highbits_set = chunk & UINT64CONST(0x8080808080808080);
+ if (highbits_set)
+ return 0;
+
+ /*
+ * Check if there are any zero bytes in this chunk.
+ *
+ * First, add 0x7f to each byte. This sets the high bit in each byte,
+ * unless it was a zero. We already checked that none of the bytes had
+ * the high bit set previously, so the max value each byte can have
+ * after the addition is 0x7f + 0x7f = 0xfe, and we don't need to
+ * worry about carrying over to the next byte.
+ */
+ highbit_carry = chunk + UINT64CONST(0x7f7f7f7f7f7f7f7f);
+
+ /* Then check that the high bit is set in each byte. */
+ highbit_carry &= UINT64CONST(0x8080808080808080);
+ if (highbit_carry == UINT64CONST(0x8080808080808080))
+ return sizeof(uint64);
+ else
+ return 0;
+ }
+ else
+ return 0;
+}
+
static int
pg_utf8_verifystr(const unsigned char *s, int len)
{
const unsigned char *start = s;
/*
- * fast path when we have enough bytes left in the string to cover all
- * valid UTF-8 sequences
+ * fast path when we have enough bytes left in the string to use bitwise operations
*/
- while (len >= 4)
+ while (len >= 8)
{
int l;
- /* check if the first byte is both non-zero and doesn't have the high bit set */
- if ((signed char) (*s) > 0)
+ /* fast path for ASCII-subset characters */
+ l = check_ascii(s, 8);
+ if (l)
{
- s++;
- len--;
+ s += l;
+ len -= l;
continue;
}
diff --git a/src/test/regress/expected/conversion.out b/src/test/regress/expected/conversion.out
index e4ab9fe765..62461063b3 100644
--- a/src/test/regress/expected/conversion.out
+++ b/src/test/regress/expected/conversion.out
@@ -141,7 +141,7 @@ with test_bytes as (
), test_padded as (
select
description,
- (test_conv(inbytes || repeat('.', 3)::bytea, 'utf8', 'utf8')).error
+ (test_conv(inbytes || repeat('.', 8)::bytea, 'utf8', 'utf8')).error
from test_bytes
)
select
diff --git a/src/test/regress/sql/conversion.sql b/src/test/regress/sql/conversion.sql
index e5a7e47958..5d0280cd1c 100644
--- a/src/test/regress/sql/conversion.sql
+++ b/src/test/regress/sql/conversion.sql
@@ -118,7 +118,7 @@ with test_bytes as (
), test_padded as (
select
description,
- (test_conv(inbytes || repeat('.', 3)::bytea, 'utf8', 'utf8')).error
+ (test_conv(inbytes || repeat('.', 8)::bytea, 'utf8', 'utf8')).error
from test_bytes
)
select
--
2.31.1