Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize StringBuilder/StringBuffer serialization #908

Merged
merged 15 commits into from
Sep 27, 2023
102 changes: 82 additions & 20 deletions java/fury-core/src/main/java/io/fury/serializer/Serializers.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@

package io.fury.serializer;

import static io.fury.util.function.Functions.makeGetterFunction;

import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
import io.fury.Fury;
import io.fury.collection.Tuple2;
import io.fury.memory.MemoryBuffer;
import io.fury.resolver.ClassResolver;
import io.fury.type.Type;
import io.fury.util.Platform;
import io.fury.util.Utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
Expand All @@ -37,6 +41,8 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.regex.Pattern;

/**
Expand Down Expand Up @@ -410,46 +416,102 @@ public Double read(MemoryBuffer buffer) {
}
}

public static final class StringBuilderSerializer extends Serializer<StringBuilder> {
private final StringSerializer stringSerializer;
static Tuple2<ToIntFunction, Function> builderCache;

private static synchronized Tuple2<ToIntFunction, Function> getBuilderFunc() {
if (builderCache == null) {
Function getValue =
(Function) makeGetterFunction(StringBuilder.class.getSuperclass(), "getValue");
if (Platform.JAVA_VERSION > 8) {
try {
Method getCoderMethod = StringBuilder.class.getSuperclass().getDeclaredMethod("getCoder");
ToIntFunction<CharSequence> getCoder =
(ToIntFunction<CharSequence>) makeGetterFunction(getCoderMethod, int.class);
builderCache = Tuple2.of(getCoder, getValue);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}

public StringBuilderSerializer(Fury fury) {
super(fury, StringBuilder.class);
} else {
builderCache = Tuple2.of(null, getValue);
}
}
return builderCache;
}

public abstract static class AbstractStringBuilderSerializer<T extends CharSequence>
extends Serializer<T> {
protected final ToIntFunction getCoder;
protected final Function getValue;
protected final StringSerializer stringSerializer;

public AbstractStringBuilderSerializer(Fury fury, Class<T> type) {
super(fury, type);
Tuple2<ToIntFunction, Function> builderFunc = getBuilderFunc();
getCoder = builderFunc.f0;
getValue = builderFunc.f1;
stringSerializer = new StringSerializer(fury);
}

pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
@Override
public void write(MemoryBuffer buffer, StringBuilder value) {
stringSerializer.writeJavaString(buffer, value.toString());
public void xwrite(MemoryBuffer buffer, T value) {
stringSerializer.writeUTF8String(buffer, value.toString());
}

@Override
public StringBuilder read(MemoryBuffer buffer) {
return new StringBuilder(stringSerializer.readJavaString(buffer));
public short getXtypeId() {
return (short) -Type.STRING.getId();
}

@Override
public void write(MemoryBuffer buffer, T value) {
if (Platform.JAVA_VERSION > 8) {
int coder = getCoder.applyAsInt(value);
byte[] v = (byte[]) getValue.apply(value);
buffer.writeByte(coder);
if (coder == 0) {
buffer.writePrimitiveArrayWithSizeEmbedded(v, Platform.BYTE_ARRAY_OFFSET, value.length());
} else {
if (coder != 1) {
throw new UnsupportedOperationException("Unsupported coder " + coder);
}
buffer.writePrimitiveArrayWithSizeEmbedded(
v, Platform.BYTE_ARRAY_OFFSET, value.length() << 1);
}
} else {
char[] v = (char[]) getValue.apply(value);
if (StringSerializer.isAscii(v)) {
stringSerializer.writeJDK8Ascii(buffer, v, value.length());
} else {
stringSerializer.writeJDK8UTF16(buffer, v, value.length());
}
}
}
}

public static final class StringBufferSerializer extends Serializer<StringBuffer> {
private final StringSerializer stringSerializer;
public static final class StringBuilderSerializer
extends AbstractStringBuilderSerializer<StringBuilder> {

public StringBufferSerializer(Fury fury) {
super(fury, StringBuffer.class);
stringSerializer = new StringSerializer(fury);
public StringBuilderSerializer(Fury fury) {
super(fury, StringBuilder.class);
}

@Override
public short getXtypeId() {
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
return (short) -Type.STRING.getId();
public StringBuilder read(MemoryBuffer buffer) {
return new StringBuilder(stringSerializer.readJavaString(buffer));
}

@Override
public void write(MemoryBuffer buffer, StringBuffer value) {
stringSerializer.writeJavaString(buffer, value.toString());
public StringBuilder xread(MemoryBuffer buffer) {
return new StringBuilder(stringSerializer.readUTF8String(buffer));
}
}

pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
@Override
public void xwrite(MemoryBuffer buffer, StringBuffer value) {
stringSerializer.writeUTF8String(buffer, value.toString());
public static final class StringBufferSerializer
extends AbstractStringBuilderSerializer<StringBuffer> {

public StringBufferSerializer(Fury fury) {
super(fury, StringBuffer.class);
}

pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ public Expression writeStringExpr(Expression strSerializer, Expression buffer, E
public void writeJava8StringCompressed(MemoryBuffer buffer, String value) {
final char[] chars = (char[]) Platform.getObject(value, STRING_VALUE_FIELD_OFFSET);
if (isAscii(chars)) {
writeJDK8Ascii(buffer, chars);
writeJDK8Ascii(buffer, chars, chars.length);
} else {
writeJDK8UTF16(buffer, chars);
writeJDK8UTF16(buffer, chars, chars.length);
}
}

Expand Down Expand Up @@ -289,9 +289,9 @@ public void writeJavaString(MemoryBuffer buffer, String value) {
final char[] chars = (char[]) Platform.getObject(value, STRING_VALUE_FIELD_OFFSET);
if (compressString) {
if (isAscii(chars)) {
writeJDK8Ascii(buffer, chars);
writeJDK8Ascii(buffer, chars, chars.length);
} else {
writeJDK8UTF16(buffer, chars);
writeJDK8UTF16(buffer, chars, chars.length);
}
} else {
int numBytes = MathUtils.doubleExact(value.length());
Expand Down Expand Up @@ -382,8 +382,7 @@ public static void writeJDK11String(MemoryBuffer buffer, String value) {
buffer.unsafeWriterIndex(writerIndex);
}

public void writeJDK8Ascii(MemoryBuffer buffer, char[] chars) {
final int strLen = chars.length;
public void writeJDK8Ascii(MemoryBuffer buffer, char[] chars, final int strLen) {
int writerIndex = buffer.writerIndex();
// The `ensure` ensure next operations are safe without bound checks,
// and inner heap buffer doesn't change.
Expand Down Expand Up @@ -413,8 +412,7 @@ public void writeJDK8Ascii(MemoryBuffer buffer, char[] chars) {
}
}

public void writeJDK8UTF16(MemoryBuffer buffer, char[] chars) {
int strLen = chars.length;
public void writeJDK8UTF16(MemoryBuffer buffer, char[] chars, int strLen) {
int numBytes = MathUtils.doubleExact(strLen);
if (Platform.IS_LITTLE_ENDIAN) {
buffer.writeByte(UTF16);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,19 @@ public static Object makeGetterFunction(Method method) {
}
}

public static Object makeGetterFunction(Method method, Class<?> returnType) {
MethodHandles.Lookup lookup = _JDKAccess._trustedLookup(method.getDeclaringClass());
try {
// Why `lookup.findGetter` doesn't work?
// MethodHandle handle = lookup.findGetter(field.getDeclaringClass(), field.getName(),
// field.getType());
MethodHandle handle = lookup.unreflect(method);
return _JDKAccess.makeGetterFunction(lookup, handle, returnType);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}

public static Tuple2<Class<?>, String> getterMethodInfo(Class<?> type) {
return _JDKAccess.getterMethodInfo(type);
}
Expand Down