Speed up lexing of long JSON strings
authorJohn Naylor <john.naylor@postgresql.org>
Wed, 31 Aug 2022 03:39:17 +0000 (10:39 +0700)
committerJohn Naylor <john.naylor@postgresql.org>
Fri, 2 Sep 2022 02:36:22 +0000 (09:36 +0700)
Use optimized linear search when looking ahead for end quotes,
backslashes, and non-printable characters. This results in nearly 40%
faster JSON parsing on x86-64 when most values are long strings, and
all platforms should see some improvement.

Reviewed by Andres Freund and Nathan Bossart
Discussion: https://www.postgresql.org/message-id/CAFBsxsGhaR2KQ5eisaK%3D6Vm60t%3DaxhD8Ckj1qFoCH1pktZi%2B2w%40mail.gmail.com
Discussion: https://www.postgresql.org/message-id/CAFBsxsESLUyJ5spfOSyPrOvKUEYYNqsBosue9SV1j8ecgNXSKA%40mail.gmail.com

src/common/jsonapi.c
src/test/regress/expected/json.out
src/test/regress/sql/json.sql

index fefd1d24d9060896fff6e888ef409406ad7d93bc..cfc025749cc4280cec0c4bb35b7fcd4539747ea8 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "common/jsonapi.h"
 #include "mb/pg_wchar.h"
+#include "port/pg_lfind.h"
 
 #ifndef FRONTEND
 #include "miscadmin.h"
@@ -844,7 +845,7 @@ json_lex_string(JsonLexContext *lex)
        }
        else
        {
-           char       *p;
+           char       *p = s;
 
            if (hi_surrogate != -1)
                return JSON_UNICODE_LOW_SURROGATE;
@@ -853,11 +854,17 @@ json_lex_string(JsonLexContext *lex)
             * Skip to the first byte that requires special handling, so we
             * can batch calls to appendBinaryStringInfo.
             */
-           for (p = s; p < end; p++)
+           while (p < end - sizeof(Vector8) &&
+                  !pg_lfind8('\\', (uint8 *) p, sizeof(Vector8)) &&
+                  !pg_lfind8('"', (uint8 *) p, sizeof(Vector8)) &&
+                  !pg_lfind8_le(31, (uint8 *) p, sizeof(Vector8)))
+               p += sizeof(Vector8);
+
+           for (; p < end; p++)
            {
                if (*p == '\\' || *p == '"')
                    break;
-               else if ((unsigned char) *p < 32)
+               else if ((unsigned char) *p <= 31)
                {
                    /* Per RFC4627, these characters MUST be escaped. */
                    /*
index e9d6e9faf290584e6813cd2ceea02afe5b05212a..cb181226e9fb9cde624e66e4ab73a56e13a09124 100644 (file)
@@ -42,6 +42,19 @@ LINE 1: SELECT '"\v"'::json;
                ^
 DETAIL:  Escape sequence "\v" is invalid.
 CONTEXT:  JSON data, line 1: "\v...
+-- Check fast path for longer strings (at least 16 bytes long)
+SELECT ('"'||repeat('.', 12)||'abc"')::json; -- OK
+       json        
+-------------------
+ "............abc"
+(1 row)
+
+SELECT ('"'||repeat('.', 12)||'abc\n"')::json; -- OK, legal escapes
+        json         
+---------------------
+ "............abc\n"
+(1 row)
+
 -- see json_encoding test for input with unicode escapes
 -- Numbers.
 SELECT '1'::json;              -- OK
index e366c6f51b65bdf128034090cc1a9192107d8092..589e0cea367b835da5f8beba99233541a32c86f1 100644 (file)
@@ -7,6 +7,11 @@ SELECT '"abc
 def"'::json;                   -- ERROR, unescaped newline in string constant
 SELECT '"\n\"\\"'::json;       -- OK, legal escapes
 SELECT '"\v"'::json;           -- ERROR, not a valid JSON escape
+
+-- Check fast path for longer strings (at least 16 bytes long)
+SELECT ('"'||repeat('.', 12)||'abc"')::json; -- OK
+SELECT ('"'||repeat('.', 12)||'abc\n"')::json; -- OK, legal escapes
+
 -- see json_encoding test for input with unicode escapes
 
 -- Numbers.