You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2026/04/21 04:09:31 UTC
(couchdb-jiffy) 20/49: Plain ASCII scan ahead optimization in string encoding
This is an automated email from the ASF dual-hosted git repository.
nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git
commit f8a7c77b6ba79de515162d7c5207a36e91fa3d4d
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 01:02:31 2026 -0400
Plain ASCII scan ahead optimization in string encoding
What's good for the gander is good for the ... duck?
Apply the same lookahead optimization from string [1] and number decoding [2] to string
and atom encoding. Hoist the index update (`e->i`) out of the loop and mark the
source pointer as restricted. Another good perf boost comes from a single final
`memcpy` instead of updating the buffer a byte at a time.
As a curiosity, while the compiler doesn't do any fancy auto-vectorization in
the while loop, it manages to use a single bitmask check instruction (bt) to
check for all three special cases `"`, `/` and '\' which is kind of neat:
```c
i++;
while(i < size && p[i] >= 0x20 && p[i] < 0x80
&& p[i] != '\"' && p[i] != '\\' && p[i] != '/'
) {i++;}
```
AT&T syntax (sorry)
```
leaq 1(%r15), %rdx ## i++
movzbl %dil, %r8d ## r8d contains p[i]
addl $-34, %r8d ## r8d -= '"'
cmpl $58, %r8d ## if r8d - '\' > 0
ja LBB12_23 ## then jump away
movabsq $288230376151719937, %rdi ## imm = 0x400000000002001
btq %r8, %rdi ## if bitmask matches " / and \
jae LBB12_23 ## then jump away
```
Using the bench benchee benchmark [3], this optimization shows decent results
for the encoding side. For the long strings case (issue 90) we get a 5x(!)
speedup. Some like GovTrack and Giphy get about a 30-40% boost. Others are
lower, and Canada, since it's number heavy (coordinates), unsurprisingly
doesn't change much.
===== Master =====
```
===== With input Blockchain =====
Name ips average deviation median 99th %
jiffy 8.58 K 116.54 us +/-50.23% 109.87 us 199.99 us
===== With input Canada =====
Name ips average deviation median 99th %
jiffy 32.70 30.58 ms +/-2.13% 30.51 ms 34.36 ms
===== With input Giphy =====
Name ips average deviation median 99th %
jiffy 780.56 1.28 ms +/-14.11% 1.23 ms 1.65 ms
===== With input GitHub =====
Name ips average deviation median 99th %
jiffy 2.35 K 0.43 ms +/-14.52% 0.40 ms 0.60 ms
===== With input GovTrack =====
Name ips average deviation median 99th %
jiffy 26.39 37.89 ms +/-3.95% 37.77 ms 47.00 ms
===== With input Issue 90 =====
Name ips average deviation median 99th %
jiffy 15.97 62.60 ms +/-6.27% 61.84 ms 96.36 ms
===== With input JSON Generator =====
Name ips average deviation median 99th %
jiffy 775.51 1.29 ms +/-12.40% 1.32 ms 1.81 ms
===== With input Pokedex =====
Name ips average deviation median 99th %
jiffy 1.35 K 0.74 ms +/-9.76% 0.74 ms 0.95 ms
===== With input UTF-8 unescaped =====
Name ips average deviation median 99th %
jiffy 8.20 K 121.88 us +/-7.75% 119.79 us 161.74 us
```
===== PR =====
```
===== With input Blockchain =====
Name ips average deviation median 99th %
jiffy 9.86 K 101.46 us +/-13.45% 96.60 us 162.25 us
===== With input Canada =====
Name ips average deviation median 99th %
jiffy 31.41 31.84 ms +/-10.76% 31.33 ms 58.22 ms
===== With input Giphy =====
Name ips average deviation median 99th %
jiffy 1.03 K 0.97 ms +/-17.42% 0.91 ms 1.38 ms
===== With input GitHub =====
Name ips average deviation median 99th %
jiffy 3.17 K 0.32 ms +/-21.45% 0.28 ms 0.51 ms
===== With input GovTrack =====
Name ips average deviation median 99th %
jiffy 39.19 25.51 ms +/-14.82% 23.80 ms 37.69 ms
===== With input Issue 90 =====
Name ips average deviation median 99th %
jiffy 84.84 11.79 ms +/-3.38% 11.71 ms 14.03 ms
===== With input JSON Generator =====
Name ips average deviation median 99th %
jiffy 1.10 K 0.91 ms +/-21.32% 0.85 ms 1.55 ms
===== With input Pokedex =====
Name ips average deviation median 99th %
jiffy 1.44 K 0.70 ms +/-48.81% 0.63 ms 1.69 ms
===== With input UTF-8 unescaped =====
Name ips average deviation median 99th %
jiffy 9.19 K 108.82 us +/-7.33% 107.02 us 141.75 us
```
[1] https://github.com/davisp/jiffy/pull/252
[2] https://github.com/davisp/jiffy/pull/254
[2] https://github.com/nickva/bench
---
c_src/encoder.c | 43 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 40 insertions(+), 3 deletions(-)
diff --git a/c_src/encoder.c b/c_src/encoder.c
index 0cf1b08..96f2c24 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -360,6 +360,7 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
unsigned char data[512];
size_t size;
+ size_t start;
size_t i;
if(!enif_get_atom(e->env, val, (char*)data, 512, ERL_NIF_LATIN1)) {
@@ -384,8 +385,25 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
if(enc_special_character(e, data[i])) {
i++;
} else if(data[i] < 0x80) {
- e->p[e->i++] = data[i];
+ // Scan ahead for plain ASCII chars which don't need escaping.
+ // Since optionally users could escape forward slashes, too, we
+ // stop on them as well
+ start = i;
i++;
+ while(i < size
+ && data[i] >= 0x20
+ && data[i] < 0x80
+ && data[i] != '\"'
+ && data[i] != '\\'
+ && data[i] != '/') {
+ i++;
+ }
+ size_t run = i - start;
+ if(!enc_ensure(e, run)) {
+ return 0;
+ }
+ memcpy(&(e->p[e->i]), &data[start], run);
+ e->i += run;
} else if(data[i] >= 0x80) {
/* The atom encoding is latin1, so we don't need validation
* as all latin1 characters are valid Unicode codepoints. */
@@ -415,11 +433,12 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
static const int MAX_ESCAPE_LEN = 12;
ErlNifBinary bin;
- unsigned char* data;
+ unsigned char* JIFFY_RESTRICT data;
size_t size;
int esc_len;
size_t ulen;
int uval;
+ size_t start;
size_t i;
if(!enif_inspect_binary(e->env, val, &bin)) {
@@ -445,7 +464,25 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
if(enc_special_character(e, data[i])) {
i++;
} else if(data[i] < 0x80) {
- e->p[e->i++] = data[i++];
+ // Scan ahead for plain ASCII char and memcpy them. Stop at quotes,
+ // backslashes, and forward slashes, since users can optionally
+ // choose to escape them too.
+ start = i;
+ i++;
+ while(i < size
+ && data[i] >= 0x20
+ && data[i] < 0x80
+ && data[i] != '\"'
+ && data[i] != '\\'
+ && data[i] != '/') {
+ i++;
+ }
+ size_t run = i - start;
+ if(!enc_ensure(e, run)) {
+ return 0;
+ }
+ memcpy(&(e->p[e->i]), &data[start], run);
+ e->i += run;
} else if(data[i] >= 0x80) {
ulen = utf8_validate(&(data[i]), size - i);