Skip to content

Commit a5fc142

Browse files
authored
perf(java): optimize scoped meta share mode perf (#1734)
## What does this PR do? This PR optimizes scoped meta share mode writing perf by about 30%: - Replace ArrayList by ObjectArray, which can save `clear` cost - Speed up copy performance when writing classdefs ## Related issues #1733 ## Does this PR introduce any user-facing change? <!-- If any user-facing interface changes, please [open an issue](https://github.com/apache/fury/issues/new/choose) describing the need to do so and update the document if necessary. --> - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change? ## Benchmark <!-- When the PR has an impact on performance (if you don't know whether the PR will have an impact on performance, you can submit the PR first, and if it will have impact on performance, the code reviewer will explain it), be sure to attach a benchmark data here. -->
1 parent c9705d1 commit a5fc142

File tree

8 files changed

+61
-36
lines changed

8 files changed

+61
-36
lines changed

java/fury-core/src/main/java/org/apache/fury/collection/ObjectArray.java

+8-7
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
* An auto-growing array which avoid checks in {@code ArrayList} and faster for {@code clear}
2626
* method.
2727
*/
28-
public final class ObjectArray {
28+
@SuppressWarnings("unchecked")
29+
public final class ObjectArray<T> {
2930
private static final int COPY_THRESHOLD = 128;
3031
private static final int NIL_ARRAY_SIZE = 1024;
3132
private static final Object[] NIL_ARRAY = new Object[NIL_ARRAY_SIZE];
@@ -41,7 +42,7 @@ public ObjectArray(int initialCapacity) {
4142
objects = new Object[initialCapacity];
4243
}
4344

44-
public void add(Object element) {
45+
public void add(T element) {
4546
Object[] objects = this.objects;
4647
int size = this.size++;
4748
if (objects.length <= size) {
@@ -53,22 +54,22 @@ public void add(Object element) {
5354
objects[size] = element;
5455
}
5556

56-
public void set(int index, Object element) {
57+
public void set(int index, T element) {
5758
objects[index] = element;
5859
}
5960

60-
public Object get(int index) {
61-
return objects[index];
61+
public T get(int index) {
62+
return (T) objects[index];
6263
}
6364

6465
/** Returns tail item or null if no available item in the array. */
65-
public Object popOrNull() {
66+
public T popOrNull() {
6667
int size = this.size;
6768
if (size == 0) {
6869
return null;
6970
}
7071
this.size = --size;
71-
return objects[size];
72+
return (T) objects[size];
7273
}
7374

7475
public void clear() {

java/fury-core/src/main/java/org/apache/fury/memory/MemoryBuffer.java

+8
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,14 @@ public boolean isOffHeap() {
233233
return heapMemory == null;
234234
}
235235

236+
/**
237+
* Returns <tt>true</tt>, if the memory buffer is backed by heap memory and memory buffer can
238+
* write to the whole memory region of underlying byte array.
239+
*/
240+
public boolean isHeapFullyWriteable() {
241+
return heapMemory != null && heapOffset == 0;
242+
}
243+
236244
/**
237245
* Get the heap byte array object.
238246
*

java/fury-core/src/main/java/org/apache/fury/meta/ClassDef.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ public String toString() {
197197

198198
/** Write class definition to buffer. */
199199
public void writeClassDef(MemoryBuffer buffer) {
200-
buffer.writeBytes(encoded);
200+
buffer.writeBytes(encoded, 0, encoded.length);
201201
}
202202

203203
/** Read class definition from buffer. */

java/fury-core/src/main/java/org/apache/fury/resolver/ClassInfo.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class ClassInfo {
4848
// class id must be less than Integer.MAX_VALUE/2 since we use bit 0 as class id flag.
4949
short classId;
5050
ClassDef classDef;
51-
public boolean needToWriteClassDef;
51+
boolean needToWriteClassDef;
5252

5353
ClassInfo(
5454
Class<?> cls,

java/fury-core/src/main/java/org/apache/fury/resolver/ClassResolver.java

+32-15
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
import org.apache.fury.collection.IdentityMap;
8989
import org.apache.fury.collection.IdentityObjectIntMap;
9090
import org.apache.fury.collection.LongMap;
91+
import org.apache.fury.collection.ObjectArray;
9192
import org.apache.fury.collection.ObjectMap;
9293
import org.apache.fury.collection.Tuple2;
9394
import org.apache.fury.config.CompatibleMode;
@@ -204,7 +205,9 @@ public class ClassResolver {
204205
private static final float loadFactor = 0.25f;
205206
private static final float furyMapLoadFactor = 0.25f;
206207
private static final int estimatedNumRegistered = 150;
207-
private static final String META_SHARE_FIELDS_INFO_KEY = "shareFieldsInfo";
208+
private static final String SET_META__CONTEXT_MSG =
209+
"Meta context must be set before serialization, "
210+
+ "please set meta context by SerializationContext.setMetaContext";
208211
private static final ClassInfo NIL_CLASS_INFO =
209212
new ClassInfo(null, null, null, null, false, null, null, ClassResolver.NO_CLASS_ID);
210213

@@ -1285,10 +1288,7 @@ public void writeClassWithMetaShare(MemoryBuffer buffer, ClassInfo classInfo) {
12851288
return;
12861289
}
12871290
MetaContext metaContext = fury.getSerializationContext().getMetaContext();
1288-
Preconditions.checkNotNull(
1289-
metaContext,
1290-
"Meta context must be set before serialization, "
1291-
+ "please set meta context by SerializationContext.setMetaContext");
1291+
assert metaContext != null : SET_META__CONTEXT_MSG;
12921292
IdentityObjectIntMap<Class<?>> classMap = metaContext.classMap;
12931293
int newId = classMap.size;
12941294
int id = classMap.putOrGet(classInfo.cls, newId);
@@ -1332,19 +1332,16 @@ boolean needToWriteClassDef(Serializer serializer) {
13321332
}
13331333

13341334
private ClassInfo readClassInfoWithMetaShare(MemoryBuffer buffer, MetaContext metaContext) {
1335-
Preconditions.checkNotNull(
1336-
metaContext,
1337-
"Meta context must be set before serialization,"
1338-
+ " please set meta context by SerializationContext.setMetaContext");
1335+
assert metaContext != null : SET_META__CONTEXT_MSG;
13391336
int header = buffer.readVarUint32Small14();
13401337
int id = header >>> 1;
13411338
if ((header & 0b1) == 0) {
13421339
return getOrUpdateClassInfo((short) id);
13431340
}
1344-
List<ClassInfo> readClassInfos = metaContext.readClassInfos;
1341+
ObjectArray<ClassInfo> readClassInfos = metaContext.readClassInfos;
13451342
ClassInfo classInfo = readClassInfos.get(id);
13461343
if (classInfo == null) {
1347-
List<ClassDef> readClassDefs = metaContext.readClassDefs;
1344+
ObjectArray<ClassDef> readClassDefs = metaContext.readClassDefs;
13481345
ClassDef classDef = readClassDefs.get(id);
13491346
Tuple2<ClassDef, ClassInfo> classDefTuple = extRegistry.classIdToDef.get(classDef.getId());
13501347
if (classDefTuple == null || classDefTuple.f1 == null) {
@@ -1433,11 +1430,31 @@ private ClassInfo getMetaSharedClassInfo(ClassDef classDef, Class<?> clz) {
14331430
*/
14341431
public void writeClassDefs(MemoryBuffer buffer) {
14351432
MetaContext metaContext = fury.getSerializationContext().getMetaContext();
1436-
buffer.writeVarUint32Small7(metaContext.writingClassDefs.size());
1437-
for (ClassDef classDef : metaContext.writingClassDefs) {
1438-
classDef.writeClassDef(buffer);
1433+
ObjectArray<ClassDef> writingClassDefs = metaContext.writingClassDefs;
1434+
final int size = writingClassDefs.size;
1435+
buffer.writeVarUint32Small7(size);
1436+
if (buffer.isHeapFullyWriteable()) {
1437+
writeClassDefs(buffer, writingClassDefs, size);
1438+
} else {
1439+
for (int i = 0; i < size; i++) {
1440+
writingClassDefs.get(i).writeClassDef(buffer);
1441+
}
1442+
}
1443+
metaContext.writingClassDefs.size = 0;
1444+
}
1445+
1446+
private void writeClassDefs(
1447+
MemoryBuffer buffer, ObjectArray<ClassDef> writingClassDefs, int size) {
1448+
int writerIndex = buffer.writerIndex();
1449+
for (int i = 0; i < size; i++) {
1450+
byte[] encoded = writingClassDefs.get(i).getEncoded();
1451+
int bytesLen = encoded.length;
1452+
buffer.ensure(writerIndex + bytesLen);
1453+
final byte[] targetArray = buffer.getHeapMemory();
1454+
System.arraycopy(encoded, 0, targetArray, writerIndex, bytesLen);
1455+
writerIndex += bytesLen;
14391456
}
1440-
metaContext.writingClassDefs.clear();
1457+
buffer.writerIndex(writerIndex);
14411458
}
14421459

14431460
/**

java/fury-core/src/main/java/org/apache/fury/resolver/MetaContext.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919

2020
package org.apache.fury.resolver;
2121

22-
import java.util.ArrayList;
23-
import java.util.List;
2422
import org.apache.fury.collection.IdentityObjectIntMap;
23+
import org.apache.fury.collection.ObjectArray;
2524
import org.apache.fury.memory.MemoryBuffer;
2625
import org.apache.fury.meta.ClassDef;
2726

@@ -34,15 +33,15 @@ public class MetaContext {
3433
public final IdentityObjectIntMap<Class<?>> classMap = new IdentityObjectIntMap<>(8, 0.4f);
3534

3635
/** Class definitions read from peer. */
37-
public final List<ClassDef> readClassDefs = new ArrayList<>();
36+
public final ObjectArray<ClassDef> readClassDefs = new ObjectArray<>();
3837

39-
public final List<ClassInfo> readClassInfos = new ArrayList<>();
38+
public final ObjectArray<ClassInfo> readClassInfos = new ObjectArray<>();
4039

4140
/**
4241
* New class definition which needs sending to peer. This will be filled up when there are new
4342
* class definition need sending, and will be cleared after writing to buffer.
4443
*
4544
* @see ClassResolver#writeClassDefs(MemoryBuffer)
4645
*/
47-
public final List<ClassDef> writingClassDefs = new ArrayList<>();
46+
public final ObjectArray<ClassDef> writingClassDefs = new ObjectArray<>();
4847
}

java/fury-core/src/main/java/org/apache/fury/resolver/SerializationContext.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ public void resetWrite() {
7474
}
7575
if (scopedMetaShareEnabled) {
7676
metaContext.classMap.clear();
77-
metaContext.writingClassDefs.clear();
77+
metaContext.writingClassDefs.size = 0;
7878
} else {
7979
metaContext = null;
8080
}
@@ -85,8 +85,8 @@ public void resetRead() {
8585
objects.clear();
8686
}
8787
if (scopedMetaShareEnabled) {
88-
metaContext.readClassInfos.clear();
89-
metaContext.readClassDefs.clear();
88+
metaContext.readClassInfos.size = 0;
89+
metaContext.readClassDefs.size = 0;
9090
} else {
9191
metaContext = null;
9292
}
@@ -98,9 +98,9 @@ public void reset() {
9898
}
9999
if (scopedMetaShareEnabled) {
100100
metaContext.classMap.clear();
101-
metaContext.writingClassDefs.clear();
102-
metaContext.readClassInfos.clear();
103-
metaContext.readClassDefs.clear();
101+
metaContext.writingClassDefs.size = 0;
102+
metaContext.readClassInfos.size = 0;
103+
metaContext.readClassDefs.size = 0;
104104
} else {
105105
metaContext = null;
106106
}

java/fury-core/src/test/java/org/apache/fury/resolver/MetaContextTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private void checkMetaShared(Fury fury, Object o) {
7979
Assert.assertEquals(fury.deserialize(bytes1), o);
8080
fury.getSerializationContext().setMetaContext(new MetaContext());
8181
Assert.assertEquals(fury.serialize(o), bytes);
82-
Assert.assertThrows(NullPointerException.class, () -> fury.serialize(o));
82+
Assert.assertThrows(AssertionError.class, () -> fury.serialize(o));
8383
}
8484

8585
// final InnerPojo will be taken as non-final for writing class def.

0 commit comments

Comments
 (0)