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: 94 additions & 8 deletions java/fury-core/src/main/java/io/fury/serializer/Serializers.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@
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 io.fury.util.function.Functions;
import io.fury.util.function.ToByteFunction;
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,7 @@
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.regex.Pattern;

/**
Expand Down Expand Up @@ -410,17 +415,89 @@ public Double read(MemoryBuffer buffer) {
}
}

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

private static synchronized Tuple2<ToByteFunction, Function> getBuilderFunc() {
if (builderCache == null) {
Function getValue =
(Function) Functions.makeGetterFunction(StringBuilder.class.getSuperclass(), "getValue");
if (Platform.JAVA_VERSION > 8) {
ToByteFunction getCoder =
(ToByteFunction)
Functions.makeGetterFunction(StringBuilder.class.getSuperclass(), "getCoder");
builderCache = Tuple2.of(getCoder, getValue);
} else {
builderCache = Tuple2.of(null, getValue);
}
}
return builderCache;
}

public StringBuilderSerializer(Fury fury) {
super(fury, StringBuilder.class);
public abstract static class AbstractStringBuilderSerializer<T> extends Serializer<T> {
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
protected final ToByteFunction getCoder;
protected final Function getValue;
protected final StringSerializer stringSerializer;

public AbstractStringBuilderSerializer(Fury fury, Class<T> type) {
super(fury, type);
Tuple2<ToByteFunction, 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 write(MemoryBuffer buffer, T value) {
if (Platform.JAVA_VERSION > 8) {
byte[] v = new byte[0];
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
byte coder = getCoder.applyAsByte(value);
try {
Method getValueMethod =
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
StringBuilder.class.getSuperclass().getDeclaredMethod("getVCoder");
getValueMethod.setAccessible(true);
Object valueArray = getValueMethod.invoke(value);
v = (byte[]) valueArray;
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
if (v.length == 0) {
return;
}
buffer.writeByte(coder);
buffer.writePositiveVarInt(v.length);
buffer.writeBytes(v, 0, v.length);
} else {
char[] v = new char[0];
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
try {
StringBuilder sb = (StringBuilder) value;
int len = sb.length();
v = new char[len];
Method getCharsMethod =
StringBuilder.class
.getSuperclass()
.getDeclaredMethod("getChars", int.class, int.class, char[].class, int.class);
getCharsMethod.setAccessible(true);
getCharsMethod.invoke(sb, 0, len, v, 0);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
if (v.length == 0) {
return;
}
if (StringSerializer.isAscii(v)) {
stringSerializer.writeJDK8Ascii(buffer, v);
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
} else {
stringSerializer.writeJDK8UTF16(buffer, v);
}
}
}
}

public static final class StringBuilderSerializer
extends AbstractStringBuilderSerializer<StringBuilder> {

public StringBuilderSerializer(Fury fury) {
super(fury, StringBuilder.class);
}

@Override
Expand All @@ -444,7 +521,11 @@ public short getXtypeId() {

@Override
public void write(MemoryBuffer buffer, StringBuffer value) {
stringSerializer.writeJavaString(buffer, value.toString());
int length = value.length();
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
buffer.writeInt(length);
for (int i = 0; i < length; i++) {
buffer.writeChar(value.charAt(i));
}
}

@Override
Expand All @@ -454,7 +535,12 @@ public void xwrite(MemoryBuffer buffer, StringBuffer value) {

pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
@Override
public StringBuffer read(MemoryBuffer buffer) {
return new StringBuffer(stringSerializer.readJavaString(buffer));
int length = buffer.readInt();
StringBuffer stringBuffer = new StringBuffer(length);
for (int i = 0; i < length; i++) {
stringBuffer.append(buffer.readChar());
}
return stringBuffer;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -705,4 +705,110 @@ public String readUTF8String(MemoryBuffer buffer) {
return new String(tmpArray, 0, numBytes, StandardCharsets.UTF_8);
}
}

public void writeJavaStringBuilder(MemoryBuffer buffer, StringBuilder value) {
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
if (Platform.JAVA_VERSION <= 8) {
writeJDK8StringBuilder(buffer, value);
} else {
writeJDK9PlusStringBuilder(buffer, value);
}
}

private void writeJDK8StringBuilder(MemoryBuffer buffer, StringBuilder value) {
int length = value.length();
buffer.writePositiveVarInt(length);
buffer.ensure(buffer.writerIndex() + length);
byte[] targetArray = buffer.getHeapMemory();
int writerIndex = buffer.writerIndex();
if (targetArray != null) {
int arrIndex = buffer.unsafeHeapWriterIndex();
for (int i = 0; i < length; i++) {
targetArray[arrIndex + i] = (byte) value.charAt(i);
}
buffer.unsafeWriterIndex(writerIndex + length);
} else {
for (int i = 0; i < length; i++) {
buffer.unsafePut(writerIndex++, (byte) value.charAt(i));
}
buffer.unsafeWriterIndex(writerIndex);
}
}

private void writeJDK9PlusStringBuilder(MemoryBuffer buffer, StringBuilder value) {
byte[] bytes = null;
if (value != null) {
bytes = getJDK9PlusInternalByteArray(value);
}
int bytesLen = 0;
if (bytes != null) {
bytesLen = bytes.length;
}
buffer.ensure(buffer.writerIndex() + 9 + bytesLen);
byte[] targetArray = buffer.getHeapMemory();
if (targetArray != null) {
int targetIndex = buffer.unsafeHeapWriterIndex();
int arrIndex = targetIndex;
targetArray[arrIndex++] = 0;
arrIndex += MemoryUtils.writePositiveVarInt(targetArray, arrIndex, bytesLen);
if (bytes != null) {
System.arraycopy(bytes, 0, targetArray, arrIndex, bytesLen);
}
buffer.unsafeWriterIndex(buffer.writerIndex() + arrIndex - targetIndex + bytesLen);
} else {
buffer.unsafePut(buffer.writerIndex(), (byte) 0);
buffer.unsafePutPositiveVarInt(buffer.writerIndex() + 1, bytesLen);
long offHeapAddress = buffer.getUnsafeAddress();
Platform.copyMemory(
bytes,
Platform.BYTE_ARRAY_OFFSET,
null,
offHeapAddress + buffer.writerIndex() + 9,
bytesLen);
buffer.unsafeWriterIndex(buffer.writerIndex() + 9 + bytesLen);
}
}

private byte[] getJDK9PlusInternalByteArray(StringBuilder sb) {
try {
Field valueField = sb.getClass().getSuperclass().getDeclaredField("value");
valueField.setAccessible(true);
return (byte[]) valueField.get(sb);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public StringBuilder readJavaStringBuilder(MemoryBuffer buffer) {
if (Platform.JAVA_VERSION <= 8) {
return readJDK8StringBuilder(buffer);
} else {
return readJDK9PlusStringBuilder(buffer);
}
}

private StringBuilder readJDK8StringBuilder(MemoryBuffer buffer) {
int length = buffer.readPositiveVarInt();
StringBuilder sb = new StringBuilder(length);
byte[] targetArray = buffer.getHeapMemory();
if (targetArray != null) {
int arrIndex = buffer.readerIndex();
for (int i = 0; i < length; i++) {
sb.append((char) (targetArray[arrIndex++] & 0xFF));
}
buffer.readerIndex(arrIndex);
} else {
for (int i = 0; i < length; i++) {
sb.append((char) (buffer.readByte() & 0xFF));
}
}
return sb;
}

private StringBuilder readJDK9PlusStringBuilder(MemoryBuffer buffer) {
byte coder = buffer.readByte();
int length = buffer.readPositiveVarInt();
byte[] bytes = new byte[length];
buffer.readBytes(bytes);
return new StringBuilder(new String(bytes, StandardCharsets.UTF_8));
}
}