Skip to content

Commit

Permalink
Micro-optimize JSONTYPE_NUMERIC code path in json.c.
Browse files Browse the repository at this point in the history
This commit does the following:

* In datum_to_json_internal(), the call to IsValidJsonNumber() is
  replaced with simplified validation code.  This avoids an extra
  call to strlen() in this path, and it avoids validating the
  entire string (which is okay since we know we're dealing with a
  numeric data type's output).

* In datum_to_json_internal(), the call to escape_json() in the
  JSONTYPE_NUMERIC path is replaced with code that just surrounds
  the string with quotes.  In passing, some other nearby calls to
  appendStringInfo() have been replaced with similar code to avoid
  unnecessary calls to vsnprintf().

* In composite_to_json(), the length of the separator is now
  determined at compile time to avoid unnecessary calls to
  strlen().

On my machine, this speeds up a benchmark for the proposed COPY TO
(FORMAT json) command with many integers by upwards of 20%.  There
are likely other code paths that could be given a similar
treatment, but that is left as a future exercise.

Reviewed-by: Jeff Davis, Tom Lane, David Rowley, John Naylor
Discussion: https://postgr.es/m/20231207231251.GB3359478%40nathanxps13
  • Loading branch information
nathan-bossart committed Dec 8, 2023
1 parent 867dd2d commit dc3f9bc
Showing 1 changed file with 29 additions and 8 deletions.
37 changes: 29 additions & 8 deletions src/backend/utils/adt/json.c
Original file line number Diff line number Diff line change
Expand Up @@ -220,37 +220,52 @@ datum_to_json_internal(Datum val, bool is_null, StringInfo result,
outputstr = OidOutputFunctionCall(outfuncoid, val);

/*
* Don't call escape_json for a non-key if it's a valid JSON
* number.
* Don't quote a non-key if it's a valid JSON number (i.e., not
* "Infinity", "-Infinity", or "NaN"). Since we know this is a
* numeric data type's output, we simplify and open-code the
* validation for better performance.
*/
if (!key_scalar && IsValidJsonNumber(outputstr, strlen(outputstr)))
if (!key_scalar &&
((*outputstr >= '0' && *outputstr <= '9') ||
(*outputstr == '-' &&
(outputstr[1] >= '0' && outputstr[1] <= '9'))))
appendStringInfoString(result, outputstr);
else
escape_json(result, outputstr);
{
appendStringInfoChar(result, '"');
appendStringInfoString(result, outputstr);
appendStringInfoChar(result, '"');
}
pfree(outputstr);
break;
case JSONTYPE_DATE:
{
char buf[MAXDATELEN + 1];

JsonEncodeDateTime(buf, val, DATEOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
appendStringInfoChar(result, '"');
appendStringInfoString(result, buf);
appendStringInfoChar(result, '"');
}
break;
case JSONTYPE_TIMESTAMP:
{
char buf[MAXDATELEN + 1];

JsonEncodeDateTime(buf, val, TIMESTAMPOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
appendStringInfoChar(result, '"');
appendStringInfoString(result, buf);
appendStringInfoChar(result, '"');
}
break;
case JSONTYPE_TIMESTAMPTZ:
{
char buf[MAXDATELEN + 1];

JsonEncodeDateTime(buf, val, TIMESTAMPTZOID, NULL);
appendStringInfo(result, "\"%s\"", buf);
appendStringInfoChar(result, '"');
appendStringInfoString(result, buf);
appendStringInfoChar(result, '"');
}
break;
case JSONTYPE_JSON:
Expand Down Expand Up @@ -503,8 +518,14 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
int i;
bool needsep = false;
const char *sep;
int seplen;

/*
* We can avoid expensive strlen() calls by precalculating the separator
* length.
*/
sep = use_line_feeds ? ",\n " : ",";
seplen = use_line_feeds ? strlen(",\n ") : strlen(",");

td = DatumGetHeapTupleHeader(composite);

Expand Down Expand Up @@ -533,7 +554,7 @@ composite_to_json(Datum composite, StringInfo result, bool use_line_feeds)
continue;

if (needsep)
appendStringInfoString(result, sep);
appendBinaryStringInfo(result, sep, seplen);
needsep = true;

attname = NameStr(att->attname);
Expand Down

0 comments on commit dc3f9bc

Please sign in to comment.