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
Original file line number Diff line number Diff line change
Expand Up @@ -420,12 +420,12 @@ public StringBuilderSerializer(Fury fury) {

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

@Override
public StringBuilder read(MemoryBuffer buffer) {
return new StringBuilder(stringSerializer.readJavaString(buffer));
return stringSerializer.readJavaStringBuilder(buffer);
}
}

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

pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
@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 +458,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 @@ -299,7 +299,6 @@ public void writeJavaString(MemoryBuffer buffer, String value) {
}
}
}

public static boolean isAscii(char[] chars) {
int numChars = chars.length;
int vectorizedLen = numChars >> 2;
Expand Down Expand Up @@ -705,4 +704,117 @@ 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 (isJDK8StringBuilder()) {
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 = getJDK9PlusInternalByteArray(value);
int 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);
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 = StringBuilder.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] charArray = (char[]) valueField.get(sb);
byte[] byteArray = new byte[charArray.length * 2];
for (int i = 0, j = 0; i < charArray.length; i++) {
char ch = charArray[i];
byteArray[j++] = (byte) (ch >> 8);
byteArray[j++] = (byte) ch;
}
return byteArray;
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}

public StringBuilder readJavaStringBuilder(MemoryBuffer buffer) {
if (isJDK8StringBuilder()) {
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) {
int length = buffer.readInt();
byte[] bytes = new byte[length];
buffer.readBytes(bytes);
return new StringBuilder(new String(bytes, StandardCharsets.UTF_8));
}

public boolean isJDK8StringBuilder() {
pandalee99 marked this conversation as resolved.
Show resolved Hide resolved
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
version = version.substring(2, 3);
} else {
int dot = version.indexOf(".");
if (dot != -1) {
version = version.substring(0, dot);
}
}
return Integer.parseInt(version) == 8;
}
}