Skip to content

Commit 6b981d4

Browse files
tglsfdcCommitfest Bot
authored andcommitted
Micro-optimize datatype conversions in datum_to_jsonb_internal.
The general case for converting to a JSONB numeric value is to run the source datatype's output function and then numeric_in, but we can do substantially better than that for integer and numeric source values. This patch improves the speed of jsonb_agg by 30% for integer input, and nearly 2X for numeric input. Sadly, the obvious idea of using float4_numeric and float8_numeric to speed up those cases doesn't work: they are actually slower than the generic coerce-via-I/O method, and not by a small amount. They might round off differently than this code has historically done, too. Leave that alone pending possible changes in those functions. We can also do better than the existing code for text/varchar/bpchar source data; this optimization is similar to one that already exists in the json_agg() code. That saves 20% or so for such inputs. Also make a couple of other minor improvements, such as not giving JSONTYPE_CAST its own special case outside the switch when it could perfectly well be handled inside, and not using dubious string hacking to detect infinity and NaN results. Discussion: https://postgr.es/m/[email protected]
1 parent 0462de0 commit 6b981d4

File tree

1 file changed

+81
-34
lines changed

1 file changed

+81
-34
lines changed

src/backend/utils/adt/jsonb.c

Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "libpq/pqformat.h"
2020
#include "miscadmin.h"
2121
#include "utils/builtins.h"
22+
#include "utils/fmgroids.h"
2223
#include "utils/json.h"
2324
#include "utils/jsonb.h"
2425
#include "utils/jsonfuncs.h"
@@ -631,7 +632,8 @@ datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result,
631632
bool key_scalar)
632633
{
633634
char *outputstr;
634-
bool numeric_error;
635+
Numeric numeric_val;
636+
bool numeric_to_string;
635637
JsonbValue jb;
636638
bool scalar_jsonb = false;
637639

@@ -656,9 +658,6 @@ datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result,
656658
}
657659
else
658660
{
659-
if (tcategory == JSONTYPE_CAST)
660-
val = OidFunctionCall1(outfuncoid, val);
661-
662661
switch (tcategory)
663662
{
664663
case JSONTYPE_ARRAY:
@@ -682,41 +681,73 @@ datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result,
682681
}
683682
break;
684683
case JSONTYPE_NUMERIC:
685-
outputstr = OidOutputFunctionCall(outfuncoid, val);
686684
if (key_scalar)
687685
{
688-
/* always quote keys */
686+
/* always stringify keys */
687+
numeric_to_string = true;
688+
numeric_val = NULL; /* pacify stupider compilers */
689+
}
690+
else
691+
{
692+
Datum numd;
693+
694+
switch (outfuncoid)
695+
{
696+
case F_NUMERIC_OUT:
697+
numeric_val = DatumGetNumeric(val);
698+
break;
699+
case F_INT2OUT:
700+
numeric_val = int64_to_numeric(DatumGetInt16(val));
701+
break;
702+
case F_INT4OUT:
703+
numeric_val = int64_to_numeric(DatumGetInt32(val));
704+
break;
705+
case F_INT8OUT:
706+
numeric_val = int64_to_numeric(DatumGetInt64(val));
707+
break;
708+
#ifdef NOT_USED
709+
710+
/*
711+
* Ideally we'd short-circuit these two cases
712+
* using float[48]_numeric. However, those
713+
* functions are currently slower than the generic
714+
* coerce-via-I/O approach. And they may round
715+
* off differently. Until/unless that gets fixed,
716+
* continue to use coerce-via-I/O for floats.
717+
*/
718+
case F_FLOAT4OUT:
719+
numd = DirectFunctionCall1(float4_numeric, val);
720+
numeric_val = DatumGetNumeric(numd);
721+
break;
722+
case F_FLOAT8OUT:
723+
numd = DirectFunctionCall1(float8_numeric, val);
724+
numeric_val = DatumGetNumeric(numd);
725+
break;
726+
#endif
727+
default:
728+
outputstr = OidOutputFunctionCall(outfuncoid, val);
729+
numd = DirectFunctionCall3(numeric_in,
730+
CStringGetDatum(outputstr),
731+
ObjectIdGetDatum(InvalidOid),
732+
Int32GetDatum(-1));
733+
numeric_val = DatumGetNumeric(numd);
734+
break;
735+
}
736+
/* Must convert to string if it's Inf or NaN */
737+
numeric_to_string = (numeric_is_inf(numeric_val) ||
738+
numeric_is_nan(numeric_val));
739+
}
740+
if (numeric_to_string)
741+
{
742+
outputstr = OidOutputFunctionCall(outfuncoid, val);
689743
jb.type = jbvString;
690744
jb.val.string.len = strlen(outputstr);
691745
jb.val.string.val = outputstr;
692746
}
693747
else
694748
{
695-
/*
696-
* Make it numeric if it's a valid JSON number, otherwise
697-
* a string. Invalid numeric output will always have an
698-
* 'N' or 'n' in it (I think).
699-
*/
700-
numeric_error = (strchr(outputstr, 'N') != NULL ||
701-
strchr(outputstr, 'n') != NULL);
702-
if (!numeric_error)
703-
{
704-
Datum numd;
705-
706-
jb.type = jbvNumeric;
707-
numd = DirectFunctionCall3(numeric_in,
708-
CStringGetDatum(outputstr),
709-
ObjectIdGetDatum(InvalidOid),
710-
Int32GetDatum(-1));
711-
jb.val.numeric = DatumGetNumeric(numd);
712-
pfree(outputstr);
713-
}
714-
else
715-
{
716-
jb.type = jbvString;
717-
jb.val.string.len = strlen(outputstr);
718-
jb.val.string.val = outputstr;
719-
}
749+
jb.type = jbvNumeric;
750+
jb.val.numeric = numeric_val;
720751
}
721752
break;
722753
case JSONTYPE_DATE:
@@ -738,6 +769,9 @@ datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result,
738769
jb.val.string.len = strlen(jb.val.string.val);
739770
break;
740771
case JSONTYPE_CAST:
772+
/* cast to JSON, and then process as JSON */
773+
val = OidFunctionCall1(outfuncoid, val);
774+
/* FALL THROUGH */
741775
case JSONTYPE_JSON:
742776
{
743777
/* parse the json right into the existing result object */
@@ -793,11 +827,24 @@ datum_to_jsonb_internal(Datum val, bool is_null, JsonbInState *result,
793827
}
794828
break;
795829
default:
796-
outputstr = OidOutputFunctionCall(outfuncoid, val);
830+
/* special-case text types to save useless palloc/memcpy ops */
831+
if (outfuncoid == F_TEXTOUT ||
832+
outfuncoid == F_VARCHAROUT ||
833+
outfuncoid == F_BPCHAROUT)
834+
{
835+
text *txt = DatumGetTextPP(val);
836+
837+
jb.val.string.len = VARSIZE_ANY_EXHDR(txt);
838+
jb.val.string.val = VARDATA_ANY(txt);
839+
}
840+
else
841+
{
842+
outputstr = OidOutputFunctionCall(outfuncoid, val);
843+
jb.val.string.len = strlen(outputstr);
844+
jb.val.string.val = outputstr;
845+
}
797846
jb.type = jbvString;
798-
jb.val.string.len = strlen(outputstr);
799847
(void) checkStringLen(jb.val.string.len, NULL);
800-
jb.val.string.val = outputstr;
801848
break;
802849
}
803850
}

0 commit comments

Comments
 (0)