This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new c727ea5a2 perf(java): optimize json serialize perf (#3834)
c727ea5a2 is described below
commit c727ea5a26e707e2cbfcb8604112323702ed0246
Author: Shawn Yang <[email protected]>
AuthorDate: Sun Jul 12 19:51:49 2026 +0530
perf(java): optimize json serialize perf (#3834)
## Why?
## What does this PR do?
## Related issues
## AI Contribution Checklist
- [ ] Substantial AI assistance was used in this PR: `yes` / `no`
- [ ] If `yes`, I included a completed [AI Contribution
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
in this PR description and the required `AI Usage Disclosure`.
- [ ] If `yes`, my PR description includes the required `ai_review`
summary and screenshot evidence or equivalent persisted links of the
final clean AI review results from both fresh reviewers described in
`AI_POLICY.md`, the Fory-guided reviewer and the independent general
reviewer, on the current PR diff or current HEAD after the latest code
changes.
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
---
.gitignore | 1 +
.../json/codegen/JsonGeneratedCodecBuilder.java | 5 +
.../fory/json/codegen/JsonReaderCodegen.java | 23 ++-
.../fory/json/codegen/JsonWriterCodegen.java | 218 ++++++++++++++++-----
.../org/apache/fory/json/meta/JsonFieldInfo.java | 32 ++-
.../apache/fory/json/writer/StringJsonWriter.java | 67 ++++---
.../apache/fory/json/writer/Utf8JsonWriter.java | 73 +++++--
7 files changed, 315 insertions(+), 104 deletions(-)
diff --git a/.gitignore b/.gitignore
index ba7a5f9ff..b93f7801d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -110,6 +110,7 @@ examples/cpp/cmake_example/build
**/benchmark_results.json
**/benchmark_report.md
**/benchmark_*.png
+.cli.params.json
benchmarks/java/reports/
**/results/
benchmarks/**/report/
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonGeneratedCodecBuilder.java
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonGeneratedCodecBuilder.java
index dfb2ec20a..3601349a8 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonGeneratedCodecBuilder.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonGeneratedCodecBuilder.java
@@ -19,6 +19,8 @@
package org.apache.fory.json.codegen;
+import static org.apache.fory.codegen.ExpressionUtils.inline;
+
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.apache.fory.builder.CodecBuilder;
@@ -123,6 +125,9 @@ final class JsonGeneratedCodecBuilder extends CodecBuilder {
}
Expression setField(JsonFieldInfo property, Expression object, Expression
value) {
+ // A parsed member value has one store owner. Keep that expression inline
so generated readers
+ // do not add a local store/load pair before the setter, field, or
VarHandle write.
+ value = inline(value);
Method setter = property.readSetter();
if (setter != null) {
Class<?> rawType = setter.getParameterTypes()[0];
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonReaderCodegen.java
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonReaderCodegen.java
index f9887019a..e7e01d0c2 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonReaderCodegen.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonReaderCodegen.java
@@ -303,7 +303,7 @@ abstract class JsonReaderCodegen {
expressions.add(
new Expression.Assign(
new Reference("this.readTable", TypeRef.of(JsonFieldTable.class)),
- new Expression.Invoke(owner, "readTable",
TypeRef.of(JsonFieldTable.class))));
+ new Expression.Invoke(owner, "readTable",
TypeRef.of(JsonFieldTable.class)).inline()));
if (record) {
expressions.add(
new Expression.Assign(new Reference("this.owner",
TypeRef.of(ObjectCodec.class)), owner));
@@ -368,9 +368,11 @@ abstract class JsonReaderCodegen {
expressions.add(returnObject(object, record));
return expressions;
}
- Expression hashes =
- new Expression.Variable("localFieldHashes", fieldRef("fieldHashes",
long[].class));
- expressions.add(hashes);
+ Expression hashes = fieldRef("fieldHashes", long[].class);
+ if (properties.length > 1) {
+ hashes = new Expression.Variable("localFieldHashes", hashes);
+ expressions.add(hashes);
+ }
Expression[] skips = new Expression[properties.length];
for (int i = 1; i < properties.length; i++) {
skips[i] = new Expression.Variable("skip" + i, Expression.Literal.False);
@@ -732,10 +734,11 @@ abstract class JsonReaderCodegen {
if (record) {
return new Expression.Variable(
"object",
- new Expression.Invoke(
- ownerRef(), "newRecordFieldValues", TypeRef.of(Object[].class),
false));
+ inline(
+ new Expression.Invoke(
+ ownerRef(), "newRecordFieldValues",
TypeRef.of(Object[].class), false)));
}
- return new Expression.Variable("object", builder.newObject());
+ return builder.newObject();
}
private Expression returnObject(Expression object, boolean record) {
@@ -744,7 +747,9 @@ abstract class JsonReaderCodegen {
return new Expression.ListExpression(
exitDepth,
new Expression.Return(
- new Expression.Invoke(ownerRef(), "newRecord",
TypeRef.of(Object.class), object)));
+ inline(
+ new Expression.Invoke(
+ ownerRef(), "newRecord", TypeRef.of(Object.class),
object))));
}
return new Expression.ListExpression(exitDepth, new
Expression.Return(object));
}
@@ -1651,7 +1656,7 @@ abstract class JsonReaderCodegen {
}
final Expression assignRecord(Expression object, int id, Expression value) {
- return new Expression.AssignArrayElem(object, value,
Expression.Literal.ofInt(id));
+ return new Expression.AssignArrayElem(object, inline(value),
Expression.Literal.ofInt(id));
}
final Expression box(Class<?> boxedType, Expression value) {
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonWriterCodegen.java
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonWriterCodegen.java
index faa53a1ed..54078a40e 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonWriterCodegen.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/codegen/JsonWriterCodegen.java
@@ -23,7 +23,6 @@ import static org.apache.fory.codegen.ExpressionUtils.add;
import static org.apache.fory.codegen.ExpressionUtils.cast;
import static org.apache.fory.codegen.ExpressionUtils.eq;
import static org.apache.fory.codegen.ExpressionUtils.inline;
-import static org.apache.fory.codegen.ExpressionUtils.valueOf;
import java.math.BigDecimal;
import java.time.LocalDate;
@@ -88,23 +87,26 @@ abstract class JsonWriterCodegen {
abstract int splitMemberThreshold();
- abstract StringPrefixFields prefixFields(JsonFieldInfo[] properties, boolean
objectStartFused);
+ abstract PrefixFields prefixFields(JsonFieldInfo[] properties, boolean
objectStartFused);
abstract void addPrefixFields(
- CodegenContext ctx, JsonFieldInfo property, int id, StringPrefixFields
fields);
+ CodegenContext ctx, JsonFieldInfo property, int id, PrefixFields fields);
abstract void addPrefixAssignments(
Expression.ListExpression expressions,
Expression property,
JsonFieldInfo field,
int id,
- StringPrefixFields fields);
+ PrefixFields fields);
abstract Reference writerRef();
abstract Expression writeObjectStartPrimitive(
JsonFieldInfo property, Expression value, Expression writer);
+ abstract Expression tryWriteObjectStartString(
+ JsonFieldInfo property, Expression value, Expression writer);
+
abstract Expression writeNumberField(
JsonFieldInfo property,
int id,
@@ -184,7 +186,7 @@ abstract class JsonWriterCodegen {
ctx.addImports(writerType());
ctx.implementsInterfaces(JsonCodegen.generatedCodecType(ctx,
objectWriterType()));
boolean objectStartFused = canFuseObjectStart(properties);
- StringPrefixFields prefixFields = prefixFields(properties,
objectStartFused);
+ PrefixFields prefixFields = prefixFields(properties, objectStartFused);
for (int i = 0; i < properties.length; i++) {
JsonFieldInfo property = properties[i];
if (usesWriteInfo(property)) {
@@ -223,11 +225,12 @@ abstract class JsonWriterCodegen {
bodyCode = "this." + objectMethod + "(writer, (" + ctx.type(type) + ")
value);\n";
} else {
ctx.clearExprState();
- Expression object =
- new Expression.Variable(
- "object",
+ Expression castObject =
+ inline(
new Expression.Cast(
new Reference("value", TypeRef.of(Object.class)),
TypeRef.of(type)));
+ Expression object =
+ properties.length <= 1 ? castObject : new
Expression.Variable("object", castObject);
Code.ExprCode body =
writeExpression(builder, properties, objectStartFused,
object).genCode(ctx);
bodyCode = body.code();
@@ -245,9 +248,8 @@ abstract class JsonWriterCodegen {
return ctx.genCode();
}
- final StringPrefixFields stringPrefixFields(
- JsonFieldInfo[] properties, boolean objectStartFused) {
- StringPrefixFields fields = new StringPrefixFields(properties.length);
+ final PrefixFields stringPrefixFields(JsonFieldInfo[] properties, boolean
objectStartFused) {
+ PrefixFields fields = new PrefixFields(properties.length);
boolean commaKnown = objectStartFused;
for (int i = 0; i < properties.length; i++) {
JsonFieldInfo property = properties[i];
@@ -268,7 +270,7 @@ abstract class JsonWriterCodegen {
}
private static void markStringUtf16PrefixField(
- JsonFieldInfo property, boolean commaKnown, StringPrefixFields fields,
int id) {
+ JsonFieldInfo property, boolean commaKnown, PrefixFields fields, int id)
{
if (!commaKnown) {
fields.name[id] = true;
fields.comma[id] = true;
@@ -279,11 +281,60 @@ abstract class JsonWriterCodegen {
}
}
- private static final class StringPrefixFields {
+ final PrefixFields utf8PrefixFields(JsonFieldInfo[] properties, boolean
objectStartFused) {
+ PrefixFields fields = new PrefixFields(properties.length);
+ boolean commaKnown = objectStartFused;
+ for (int i = 0; i < properties.length; i++) {
+ JsonFieldInfo property = properties[i];
+ if (usesPrefix(property)) {
+ if (i == 0
+ && !objectStartFused
+ && !writeNullFields
+ && Utf8Generator.canPackObjectStartString(property)) {
+ // The generated first-field branch consumes neither ordinary prefix
field.
+ } else if (objectStartFused && i == 0) {
+ if (!canPackPrefix(property, false)) {
+ fields.name[i] = true;
+ }
+ } else if (!commaKnown) {
+ if (!canUsePackedDynamicPrefix(property)
+ || !canPackSinglePrefix(property, false)
+ || !canPackSinglePrefix(property, true)) {
+ fields.name[i] = true;
+ fields.comma[i] = true;
+ }
+ } else if (!canPackPrefix(property, true)) {
+ fields.comma[i] = true;
+ }
+ }
+ if (writeNullFields || property.writeRawType().isPrimitive()) {
+ commaKnown = true;
+ }
+ }
+ return fields;
+ }
+
+ private boolean canUsePackedDynamicPrefix(JsonFieldInfo property) {
+ if (writeNullFields && !property.writeRawType().isPrimitive()) {
+ return false;
+ }
+ switch (property.writeKind()) {
+ case BYTE:
+ case SHORT:
+ case INT:
+ case LONG:
+ case STRING:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static final class PrefixFields {
private final boolean[] name;
private final boolean[] comma;
- private StringPrefixFields(int size) {
+ private PrefixFields(int size) {
name = new boolean[size];
comma = new boolean[size];
}
@@ -301,7 +352,7 @@ abstract class JsonWriterCodegen {
}
private Expression writerConstructorExpression(
- JsonFieldInfo[] properties, StringPrefixFields prefixFields) {
+ JsonFieldInfo[] properties, PrefixFields prefixFields) {
Expression.ListExpression expressions = new Expression.ListExpression();
Reference propertiesRef = new Reference("properties",
TypeRef.of(JsonFieldInfo[].class));
Reference codecsRef = new Reference("codecs",
TypeRef.of(codecArrayType()));
@@ -337,20 +388,49 @@ abstract class JsonWriterCodegen {
Expression.ListExpression expressions = new Expression.ListExpression();
expressions.add(object);
Expression index = null;
+ int firstProperty = 0;
if (!objectStartFused) {
- expressions.add(new Expression.Invoke(writer, "writeObjectStart"));
index = new Expression.Variable("index", Expression.Literal.ofInt(0));
- expressions.add(index);
+ JsonFieldInfo first = properties.length == 0 ? null : properties[0];
+ Expression value =
+ first != null && !writeNullFields && first.writeKind() ==
JsonFieldKind.STRING
+ ? new Expression.Variable(
+ "v0", cast(inline(builder.fieldValue(first, object)),
TypeRef.of(String.class)))
+ : null;
+ Expression fusedStart =
+ value == null ? null : tryWriteObjectStartString(first, value,
writer);
+ if (fusedStart != null) {
+ expressions.add(value);
+ boolean hasRemainingProperties = properties.length > 1;
+ if (hasRemainingProperties) {
+ expressions.add(index);
+ }
+ Expression present = fusedStart;
+ if (hasRemainingProperties) {
+ present =
+ new Expression.ListExpression(
+ fusedStart, new Expression.Assign(index,
Expression.Literal.ofInt(1)));
+ }
+ expressions.add(
+ new Expression.If(
+ ne(value, new Expression.Null(TypeRef.of(String.class),
false)),
+ present,
+ new Expression.Invoke(writer, "writeObjectStart")));
+ firstProperty = 1;
+ } else {
+ expressions.add(new Expression.Invoke(writer, "writeObjectStart"));
+ expressions.add(index);
+ }
}
boolean commaKnown = objectStartFused;
boolean splitMembers = properties.length >= splitMemberThreshold();
List<Expression> memberGroup = splitMembers ? new
ArrayList<>(MAX_MEMBERS_PER_METHOD) : null;
- for (int i = 0; i < properties.length; i++) {
+ for (int i = firstProperty; i < properties.length; i++) {
Expression member;
if (objectStartFused && i == 0) {
member =
writeObjectStartPrimitive(
- properties[i], builder.fieldValue(properties[i], object),
writer);
+ properties[i], inline(builder.fieldValue(properties[i],
object)), writer);
} else {
member = writeProp(builder, properties[i], i, commaKnown, index,
object, writer);
}
@@ -440,15 +520,13 @@ abstract class JsonWriterCodegen {
Expression writer) {
Class<?> rawType = property.writeRawType();
if (rawType.isPrimitive()) {
- Expression fieldValue = builder.fieldValue(property, object);
+ // Primitive members cannot be null and this path consumes the access
once. Nullable
+ // references stay cached below because their null check and write must
share one value.
+ Expression fieldValue = inline(builder.fieldValue(property, object));
if (property.writeKind() == JsonFieldKind.OBJECT) {
- Expression value =
- new Expression.Variable(
- "v" + id, valueOf(TypeRef.of(rawType).wrap(),
inline(fieldValue)));
return new Expression.ListExpression(
- value,
writeFieldName(property, id, commaKnown, index, writer),
- writeCodec(property, id, value, writer));
+ writeCodec(property, id, fieldValue, writer));
}
return writePrimitive(property, id, fieldValue, commaKnown, index,
writer);
}
@@ -624,6 +702,19 @@ abstract class JsonWriterCodegen {
return args;
}
+ private static Expression[] objectPackedPrefixArgs(JsonFieldInfo property,
Expression value) {
+ byte[] namePrefix = property.utf8NamePrefix();
+ byte[] prefix = new byte[namePrefix.length + 1];
+ prefix[0] = '{';
+ System.arraycopy(namePrefix, 0, prefix, 1, namePrefix.length);
+ return new Expression[] {
+ Expression.Literal.ofLong(packedPrefixWord(prefix, 0)),
+ Expression.Literal.ofLong(packedPrefixWord(prefix, Long.BYTES)),
+ Expression.Literal.ofInt(prefix.length),
+ value
+ };
+ }
+
private static Expression[] stringPackedPrefixArgs(
JsonFieldInfo property, int id, boolean comma, Expression... extraArgs) {
byte[] prefix =
@@ -809,13 +900,12 @@ abstract class JsonWriterCodegen {
}
@Override
- StringPrefixFields prefixFields(JsonFieldInfo[] properties, boolean
objectStartFused) {
+ PrefixFields prefixFields(JsonFieldInfo[] properties, boolean
objectStartFused) {
return stringPrefixFields(properties, objectStartFused);
}
@Override
- void addPrefixFields(
- CodegenContext ctx, JsonFieldInfo property, int id, StringPrefixFields
fields) {
+ void addPrefixFields(CodegenContext ctx, JsonFieldInfo property, int id,
PrefixFields fields) {
ctx.addField(byte[].class, "s" + id);
ctx.addField(byte[].class, "sc" + id);
if (fields.name[id]) {
@@ -832,7 +922,7 @@ abstract class JsonWriterCodegen {
Expression property,
JsonFieldInfo field,
int id,
- StringPrefixFields fields) {
+ PrefixFields fields) {
expressions.add(
new Expression.Assign(
stringPrefixRef(false, id),
@@ -873,10 +963,10 @@ abstract class JsonWriterCodegen {
case BYTE:
case SHORT:
case INT:
- method = "writeObjectIntField";
+ method = "writeObjectStartWithIntField";
break;
case LONG:
- method = "writeObjectLongField";
+ method = "writeObjectStartWithLongField";
break;
default:
throw new ForyJsonException(
@@ -890,6 +980,12 @@ abstract class JsonWriterCodegen {
writer, method, stringPrefixRef(false, 0), utf16PrefixRef(false, 0),
value);
}
+ @Override
+ Expression tryWriteObjectStartString(
+ JsonFieldInfo property, Expression value, Expression writer) {
+ return null;
+ }
+
@Override
Expression writeNumberField(
JsonFieldInfo property,
@@ -1050,15 +1146,18 @@ abstract class JsonWriterCodegen {
}
@Override
- StringPrefixFields prefixFields(JsonFieldInfo[] properties, boolean
objectStartFused) {
- return null;
+ PrefixFields prefixFields(JsonFieldInfo[] properties, boolean
objectStartFused) {
+ return utf8PrefixFields(properties, objectStartFused);
}
@Override
- void addPrefixFields(
- CodegenContext ctx, JsonFieldInfo property, int id, StringPrefixFields
fields) {
- ctx.addField(byte[].class, "u" + id);
- ctx.addField(byte[].class, "uc" + id);
+ void addPrefixFields(CodegenContext ctx, JsonFieldInfo property, int id,
PrefixFields fields) {
+ if (fields.name[id]) {
+ ctx.addField(byte[].class, "u" + id);
+ }
+ if (fields.comma[id]) {
+ ctx.addField(byte[].class, "uc" + id);
+ }
}
@Override
@@ -1067,17 +1166,21 @@ abstract class JsonWriterCodegen {
Expression property,
JsonFieldInfo field,
int id,
- StringPrefixFields fields) {
- expressions.add(
- new Expression.Assign(
- utf8PrefixRef(false, id),
- new Expression.Invoke(property, "utf8NamePrefix",
TypeRef.of(byte[].class))
- .inline()));
- expressions.add(
- new Expression.Assign(
- utf8PrefixRef(true, id),
- new Expression.Invoke(property, "utf8CommaNamePrefix",
TypeRef.of(byte[].class))
- .inline()));
+ PrefixFields fields) {
+ if (fields.name[id]) {
+ expressions.add(
+ new Expression.Assign(
+ utf8PrefixRef(false, id),
+ new Expression.Invoke(property, "utf8NamePrefix",
TypeRef.of(byte[].class))
+ .inline()));
+ }
+ if (fields.comma[id]) {
+ expressions.add(
+ new Expression.Assign(
+ utf8PrefixRef(true, id),
+ new Expression.Invoke(property, "utf8CommaNamePrefix",
TypeRef.of(byte[].class))
+ .inline()));
+ }
}
@Override
@@ -1093,10 +1196,10 @@ abstract class JsonWriterCodegen {
case BYTE:
case SHORT:
case INT:
- method = "writeObjectIntField";
+ method = "writeObjectStartWithIntField";
break;
case LONG:
- method = "writeObjectLongField";
+ method = "writeObjectStartWithLongField";
break;
default:
throw new ForyJsonException(
@@ -1108,6 +1211,21 @@ abstract class JsonWriterCodegen {
return new Expression.Invoke(writer, method, utf8PrefixRef(false, 0),
value);
}
+ @Override
+ Expression tryWriteObjectStartString(
+ JsonFieldInfo property, Expression value, Expression writer) {
+ if (!canPackObjectStartString(property)) {
+ return null;
+ }
+ return new Expression.Invoke(
+ writer, "writeObjectStartWithStringField",
objectPackedPrefixArgs(property, value));
+ }
+
+ private static boolean canPackObjectStartString(JsonFieldInfo property) {
+ return property.writeKind() == JsonFieldKind.STRING
+ && property.utf8NamePrefix().length < Long.BYTES * 2;
+ }
+
@Override
Expression writeNumberField(
JsonFieldInfo property,
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java
b/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java
index ecbe09468..4c1b0e7b5 100644
--- a/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java
+++ b/java/fory-json/src/main/java/org/apache/fory/json/meta/JsonFieldInfo.java
@@ -97,6 +97,10 @@ public final class JsonFieldInfo {
private final byte[] stringUtf16CommaNamePrefix;
private final byte[] utf8NamePrefix;
private final byte[] utf8CommaNamePrefix;
+ private final long utf8NamePrefixWord0;
+ private final long utf8NamePrefixWord1;
+ private final long utf8CommaNamePrefixWord0;
+ private final long utf8CommaNamePrefixWord1;
private final byte[][] stringEnumValues;
private final byte[][] stringElementEnumValues;
private final byte[][] stringEnumNameValues;
@@ -165,6 +169,10 @@ public final class JsonFieldInfo {
stringUtf16CommaNamePrefix = toUtf16Bytes(stringCommaNamePrefix);
utf8NamePrefix = utf8Prefix.getBytes(StandardCharsets.UTF_8);
utf8CommaNamePrefix = ("," + utf8Prefix).getBytes(StandardCharsets.UTF_8);
+ utf8NamePrefixWord0 = packedPrefixWord(utf8NamePrefix, 0);
+ utf8NamePrefixWord1 = packedPrefixWord(utf8NamePrefix, Long.BYTES);
+ utf8CommaNamePrefixWord0 = packedPrefixWord(utf8CommaNamePrefix, 0);
+ utf8CommaNamePrefixWord1 = packedPrefixWord(utf8CommaNamePrefix,
Long.BYTES);
stringEnumValues = writeKind == JsonFieldKind.ENUM ?
stringEnumValues(writeRawType) : null;
stringEnumNameValues =
writeKind == JsonFieldKind.ENUM ? fieldValues(stringNamePrefix,
stringEnumValues) : null;
@@ -994,8 +1002,21 @@ public final class JsonFieldInfo {
if (value == null) {
writer.writeFieldName(this, index);
writer.writeNull();
+ } else if (index == 0) {
+ // Keep interpreted packable fields on the same concrete writer entry as
generated codecs.
+ // A separate byte-array entry leaves the packed encoder cold until
generated parents compile,
+ // allowing C2 to copy the complete encoder graph into those parents
instead.
+ if (utf8NamePrefix.length <= Long.BYTES * 2) {
+ writer.writeStringField(
+ utf8NamePrefixWord0, utf8NamePrefixWord1, utf8NamePrefix.length,
value);
+ } else {
+ writer.writeStringField(utf8NamePrefix, value);
+ }
+ } else if (utf8CommaNamePrefix.length <= Long.BYTES * 2) {
+ writer.writeStringField(
+ utf8CommaNamePrefixWord0, utf8CommaNamePrefixWord1,
utf8CommaNamePrefix.length, value);
} else {
- writer.writeStringField(utf8NamePrefix, utf8CommaNamePrefix, index,
value);
+ writer.writeStringField(utf8CommaNamePrefix, value);
}
return true;
}
@@ -1193,4 +1214,13 @@ public final class JsonFieldInfo {
}
return utf16;
}
+
+ private static long packedPrefixWord(byte[] prefix, int offset) {
+ long word = 0;
+ int end = Math.min(prefix.length, offset + Long.BYTES);
+ for (int i = offset; i < end; i++) {
+ word |= (prefix[i] & 0xffL) << ((i - offset) << 3);
+ }
+ return word;
+ }
}
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
b/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
index 0be50cbf9..d2dd423eb 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/writer/StringJsonWriter.java
@@ -657,25 +657,25 @@ public final class StringJsonWriter extends JsonWriter
implements Appendable {
writeIntUtf16NoEnsure(value);
}
- public void writeObjectIntField(byte[] namePrefix, int value) {
+ public void writeObjectStartWithIntField(byte[] namePrefix, int value) {
enterDepth();
if (coder == LATIN1) {
- writeObjectIntFieldLatin1(namePrefix, value);
+ writeObjectStartWithIntFieldLatin1(namePrefix, value);
return;
}
- writeObjectIntFieldUtf16(namePrefix, value);
+ writeObjectStartWithIntFieldUtf16(namePrefix, value);
}
- public void writeObjectIntField(byte[] namePrefix, byte[] utf16NamePrefix,
int value) {
+ public void writeObjectStartWithIntField(byte[] namePrefix, byte[]
utf16NamePrefix, int value) {
enterDepth();
if (coder == LATIN1) {
- writeObjectIntFieldLatin1(namePrefix, value);
+ writeObjectStartWithIntFieldLatin1(namePrefix, value);
return;
}
- writeObjectIntFieldUtf16Value(utf16NamePrefix, value);
+ writeObjectStartWithIntFieldUtf16Value(utf16NamePrefix, value);
}
- public void writeObjectIntField(
+ public void writeObjectStartWithIntField(
byte[] namePrefix,
long utf16Prefix0,
long utf16Prefix1,
@@ -685,35 +685,35 @@ public final class StringJsonWriter extends JsonWriter
implements Appendable {
int value) {
enterDepth();
if (coder == LATIN1) {
- writeObjectIntFieldLatin1(namePrefix, value);
+ writeObjectStartWithIntFieldLatin1(namePrefix, value);
return;
}
- writeObjectIntFieldUtf16Packed(
+ writeObjectStartWithIntFieldUtf16Packed(
utf16Prefix0, utf16Prefix1, utf16Prefix2, utf16Prefix3,
utf16PrefixLength, value);
}
- private void writeObjectIntFieldLatin1(byte[] namePrefix, int value) {
+ private void writeObjectStartWithIntFieldLatin1(byte[] namePrefix, int
value) {
ensure(namePrefix.length + 12);
buffer[position++] = (byte) '{';
writeRawLatin1NoEnsure(namePrefix);
writeIntNoEnsure(value);
}
- private void writeObjectIntFieldUtf16(byte[] namePrefix, int value) {
+ private void writeObjectStartWithIntFieldUtf16(byte[] namePrefix, int value)
{
ensure(((namePrefix.length + 1) << 1) + 22);
writeUtf16ByteNoEnsure((byte) '{');
writeRawUtf16NoEnsure(namePrefix);
writeIntUtf16NoEnsure(value);
}
- private void writeObjectIntFieldUtf16Value(byte[] utf16NamePrefix, int
value) {
+ private void writeObjectStartWithIntFieldUtf16Value(byte[] utf16NamePrefix,
int value) {
ensure(utf16NamePrefix.length + 24);
writeUtf16ByteNoEnsure((byte) '{');
writeRawUtf16ValueNoEnsure(utf16NamePrefix);
writeIntUtf16NoEnsure(value);
}
- private void writeObjectIntFieldUtf16Packed(
+ private void writeObjectStartWithIntFieldUtf16Packed(
long utf16Prefix0,
long utf16Prefix1,
long utf16Prefix2,
@@ -809,25 +809,25 @@ public final class StringJsonWriter extends JsonWriter
implements Appendable {
writeLongUtf16NoEnsure(value);
}
- public void writeObjectLongField(byte[] namePrefix, long value) {
+ public void writeObjectStartWithLongField(byte[] namePrefix, long value) {
enterDepth();
if (coder == LATIN1) {
- writeObjectLongFieldLatin1(namePrefix, value);
+ writeObjectStartWithLongFieldLatin1(namePrefix, value);
return;
}
- writeObjectLongFieldUtf16(namePrefix, value);
+ writeObjectStartWithLongFieldUtf16(namePrefix, value);
}
- public void writeObjectLongField(byte[] namePrefix, byte[] utf16NamePrefix,
long value) {
+ public void writeObjectStartWithLongField(byte[] namePrefix, byte[]
utf16NamePrefix, long value) {
enterDepth();
if (coder == LATIN1) {
- writeObjectLongFieldLatin1(namePrefix, value);
+ writeObjectStartWithLongFieldLatin1(namePrefix, value);
return;
}
- writeObjectLongFieldUtf16Value(utf16NamePrefix, value);
+ writeObjectStartWithLongFieldUtf16Value(utf16NamePrefix, value);
}
- public void writeObjectLongField(
+ public void writeObjectStartWithLongField(
byte[] namePrefix,
long utf16Prefix0,
long utf16Prefix1,
@@ -837,35 +837,35 @@ public final class StringJsonWriter extends JsonWriter
implements Appendable {
long value) {
enterDepth();
if (coder == LATIN1) {
- writeObjectLongFieldLatin1(namePrefix, value);
+ writeObjectStartWithLongFieldLatin1(namePrefix, value);
return;
}
- writeObjectLongFieldUtf16Packed(
+ writeObjectStartWithLongFieldUtf16Packed(
utf16Prefix0, utf16Prefix1, utf16Prefix2, utf16Prefix3,
utf16PrefixLength, value);
}
- private void writeObjectLongFieldLatin1(byte[] namePrefix, long value) {
+ private void writeObjectStartWithLongFieldLatin1(byte[] namePrefix, long
value) {
ensure(namePrefix.length + 21);
buffer[position++] = (byte) '{';
writeRawLatin1NoEnsure(namePrefix);
writeLongNoEnsure(value);
}
- private void writeObjectLongFieldUtf16(byte[] namePrefix, long value) {
+ private void writeObjectStartWithLongFieldUtf16(byte[] namePrefix, long
value) {
ensure(((namePrefix.length + 1) << 1) + 40);
writeUtf16ByteNoEnsure((byte) '{');
writeRawUtf16NoEnsure(namePrefix);
writeLongUtf16NoEnsure(value);
}
- private void writeObjectLongFieldUtf16Value(byte[] utf16NamePrefix, long
value) {
+ private void writeObjectStartWithLongFieldUtf16Value(byte[] utf16NamePrefix,
long value) {
ensure(utf16NamePrefix.length + 42);
writeUtf16ByteNoEnsure((byte) '{');
writeRawUtf16ValueNoEnsure(utf16NamePrefix);
writeLongUtf16NoEnsure(value);
}
- private void writeObjectLongFieldUtf16Packed(
+ private void writeObjectStartWithLongFieldUtf16Packed(
long utf16Prefix0,
long utf16Prefix1,
long utf16Prefix2,
@@ -2208,11 +2208,18 @@ public final class StringJsonWriter extends JsonWriter
implements Appendable {
return ch > 0x1F && ch != '"' && ch != '\\';
}
+ // Keep the exact uncommon fallback outside these per-word predicates.
Folding it back in makes
+ // the standalone predicates too large for C2 to inline into the
compact-string writers.
private static boolean isJsonAsciiWord(long word) {
long notBackslash = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES) &
HIGH_BITS;
if ((notBackslash & (word + ASCII_GT_QUOTE_OFFSET)) == HIGH_BITS) {
return true;
}
+ return isJsonAsciiWordFallback(word);
+ }
+
+ private static boolean isJsonAsciiWordFallback(long word) {
+ long notBackslash = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES) &
HIGH_BITS;
return (((word + ASCII_CONTROL_OFFSET) & ~word) & HIGH_BITS) == HIGH_BITS
&& (((word ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS) ==
HIGH_BITS
&& notBackslash == HIGH_BITS;
@@ -2229,6 +2236,10 @@ public final class StringJsonWriter extends JsonWriter
implements Appendable {
== HIGH_BITS) {
return true;
}
+ return isJsonAsciiWordsFallback(word0, word1, notBackslash);
+ }
+
+ private static boolean isJsonAsciiWordsFallback(long word0, long word1, long
notBackslash) {
return ((word0 + ASCII_CONTROL_OFFSET)
& (word1 + ASCII_CONTROL_OFFSET)
& ((word0 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
@@ -2242,6 +2253,10 @@ public final class StringJsonWriter extends JsonWriter
implements Appendable {
if ((notBackslash & (word + INT_ASCII_GT_QUOTE_OFFSET)) == INT_HIGH_BITS) {
return true;
}
+ return isJsonAsciiIntFallback(word, notBackslash);
+ }
+
+ private static boolean isJsonAsciiIntFallback(int word, int notBackslash) {
return (((word + INT_ASCII_CONTROL_OFFSET) & ~word) & INT_HIGH_BITS) ==
INT_HIGH_BITS
&& (((word ^ INT_QUOTE_BYTES_COMPLEMENT) + INT_ONE_BYTES) &
INT_HIGH_BITS) == INT_HIGH_BITS
&& notBackslash == INT_HIGH_BITS;
diff --git
a/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java
b/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java
index 0808b8029..1bad1f562 100644
---
a/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java
+++
b/java/fory-json/src/main/java/org/apache/fory/json/writer/Utf8JsonWriter.java
@@ -562,7 +562,7 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
writeIntNoEnsure(value);
}
- public void writeObjectIntField(byte[] namePrefix, int value) {
+ public void writeObjectStartWithIntField(byte[] namePrefix, int value) {
enterDepth();
ensure(namePrefix.length + 12);
buffer[position++] = (byte) '{';
@@ -570,7 +570,8 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
writeIntNoEnsure(value);
}
- public void writeObjectIntField(long prefix0, long prefix1, int
prefixLength, int value) {
+ public void writeObjectStartWithIntField(
+ long prefix0, long prefix1, int prefixLength, int value) {
enterDepth();
ensurePackedPrefix(prefixLength, 12);
buffer[position++] = (byte) '{';
@@ -595,7 +596,7 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
writeLongNoEnsure(value);
}
- public void writeObjectLongField(byte[] namePrefix, long value) {
+ public void writeObjectStartWithLongField(byte[] namePrefix, long value) {
enterDepth();
ensure(namePrefix.length + 21);
buffer[position++] = (byte) '{';
@@ -603,7 +604,8 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
writeLongNoEnsure(value);
}
- public void writeObjectLongField(long prefix0, long prefix1, int
prefixLength, long value) {
+ public void writeObjectStartWithLongField(
+ long prefix0, long prefix1, int prefixLength, long value) {
enterDepth();
ensurePackedPrefix(prefixLength, 21);
buffer[position++] = (byte) '{';
@@ -611,6 +613,12 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
writeLongNoEnsure(value);
}
+ public void writeObjectStartWithStringField(
+ long prefix0, long prefix1, int prefixLength, String value) {
+ enterDepth();
+ writeStringField(prefix0, prefix1, prefixLength, value);
+ }
+
public void writeStringField(byte[] namePrefix, byte[] commaNamePrefix, int
index, String value) {
byte[] prefix = index == 0 ? namePrefix : commaNamePrefix;
writeStringField(prefix, value);
@@ -1610,6 +1618,8 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
return ch > 0x1F && ch < 0x80 && ch != '"' && ch != '\\';
}
+ // Keep the exact uncommon fallback outside these per-word predicates.
Folding it back in makes
+ // the standalone predicates too large for C2 to inline into the
short-string writers.
private static boolean isJsonAsciiWord(long word) {
long notBackslashMask = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES)
& HIGH_BITS;
// Common unescaped bytes are greater than '"' and not '\\'. The fallback
keeps the exact
@@ -1617,6 +1627,11 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
if ((notBackslashMask & (word + ASCII_GT_QUOTE_OFFSET)) == HIGH_BITS) {
return true;
}
+ return isJsonAsciiWordFallback(word);
+ }
+
+ private static boolean isJsonAsciiWordFallback(long word) {
+ long notBackslashMask = ((word ^ BACKSLASH_BYTES_COMPLEMENT) + ONE_BYTES)
& HIGH_BITS;
return (((word + ASCII_CONTROL_OFFSET) & ~word) & HIGH_BITS) == HIGH_BITS
&& (((word ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES) & HIGH_BITS) ==
HIGH_BITS
&& notBackslashMask == HIGH_BITS;
@@ -1633,12 +1648,7 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
== HIGH_BITS) {
return true;
}
- return ((word0 + ASCII_CONTROL_OFFSET)
- & (word1 + ASCII_CONTROL_OFFSET)
- & ((word0 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
- & ((word1 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
- & notBackslashMask)
- == HIGH_BITS;
+ return isJsonAsciiWordsFallback(word0, word1, notBackslashMask);
}
private static boolean isJsonAsciiWords(long word0, long word1, long word2) {
@@ -1654,14 +1664,7 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
== HIGH_BITS) {
return true;
}
- return ((word0 + ASCII_CONTROL_OFFSET)
- & (word1 + ASCII_CONTROL_OFFSET)
- & (word2 + ASCII_CONTROL_OFFSET)
- & ((word0 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
- & ((word1 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
- & ((word2 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
- & notBackslashMask)
- == HIGH_BITS;
+ return isJsonAsciiWordsFallback(word0, word1, word2, notBackslashMask);
}
private static boolean isJsonAsciiWords(long word0, long word1, long word2,
long word3) {
@@ -1679,6 +1682,32 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
== HIGH_BITS) {
return true;
}
+ return isJsonAsciiWordsFallback(word0, word1, word2, word3,
notBackslashMask);
+ }
+
+ private static boolean isJsonAsciiWordsFallback(long word0, long word1, long
notBackslashMask) {
+ return ((word0 + ASCII_CONTROL_OFFSET)
+ & (word1 + ASCII_CONTROL_OFFSET)
+ & ((word0 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
+ & ((word1 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
+ & notBackslashMask)
+ == HIGH_BITS;
+ }
+
+ private static boolean isJsonAsciiWordsFallback(
+ long word0, long word1, long word2, long notBackslashMask) {
+ return ((word0 + ASCII_CONTROL_OFFSET)
+ & (word1 + ASCII_CONTROL_OFFSET)
+ & (word2 + ASCII_CONTROL_OFFSET)
+ & ((word0 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
+ & ((word1 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
+ & ((word2 ^ QUOTE_BYTES_COMPLEMENT) + ONE_BYTES)
+ & notBackslashMask)
+ == HIGH_BITS;
+ }
+
+ private static boolean isJsonAsciiWordsFallback(
+ long word0, long word1, long word2, long word3, long notBackslashMask) {
return ((word0 + ASCII_CONTROL_OFFSET)
& (word1 + ASCII_CONTROL_OFFSET)
& (word2 + ASCII_CONTROL_OFFSET)
@@ -1697,6 +1726,10 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
if ((notBackslashMask & (word + INT_ASCII_GT_QUOTE_OFFSET)) ==
INT_HIGH_BITS) {
return true;
}
+ return isJsonAsciiIntFallback(word, notBackslashMask);
+ }
+
+ private static boolean isJsonAsciiIntFallback(int word, int
notBackslashMask) {
return (((word + INT_ASCII_CONTROL_OFFSET) & ~word) & INT_HIGH_BITS) ==
INT_HIGH_BITS
&& (((word ^ INT_QUOTE_BYTES_COMPLEMENT) + INT_ONE_BYTES) &
INT_HIGH_BITS) == INT_HIGH_BITS
&& notBackslashMask == INT_HIGH_BITS;
@@ -1708,6 +1741,10 @@ public final class Utf8JsonWriter extends JsonWriter
implements Appendable {
if ((notBackslashMask & (word + SHORT_ASCII_GT_QUOTE_OFFSET)) ==
SHORT_HIGH_BITS) {
return true;
}
+ return isJsonAsciiShortFallback(word, notBackslashMask);
+ }
+
+ private static boolean isJsonAsciiShortFallback(int word, int
notBackslashMask) {
return (((word + SHORT_ASCII_CONTROL_OFFSET) & ~word) & SHORT_HIGH_BITS)
== SHORT_HIGH_BITS
&& (((word ^ SHORT_QUOTE_BYTES_COMPLEMENT) + SHORT_ONE_BYTES) &
SHORT_HIGH_BITS)
== SHORT_HIGH_BITS
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]