优化了清理算法,提升了性能。简单类型测试中,超出 Fury50% 的性能。读取性能两者相当,JfireSE 稍差 1%。

master
linbin 2024-09-13 23:25:03 +08:00
parent cc1ac66838
commit 6de117bdce
10 changed files with 174 additions and 146 deletions

View File

@ -1,7 +1,6 @@
package com.jfirer.se2;
import com.jfirer.baseutil.reflect.ReflectUtil;
import com.jfirer.fse.InternalByteArray;
import io.github.karlatemp.unsafeaccessor.Unsafe;
import java.lang.invoke.LambdaMetafactory;
@ -80,24 +79,11 @@ public class ByteArray
writerIndex = array.length;
}
public static com.jfirer.fse.ByteArray allocate(int size)
public void resetFor(byte[] bytes)
{
return new InternalByteArray(size);
}
public static com.jfirer.fse.ByteArray allocate()
{
return new InternalByteArray(1024);
}
public static com.jfirer.fse.ByteArray wrap(byte[] array)
{
return new InternalByteArray(array);
}
public void setNeedCheck(boolean needCheck)
{
this.needCheck = needCheck;
this.array = bytes;
readerIndex = 0;
writerIndex = array.length;
}
protected void ensureNewWriterIndex(int newWriterIndex)
@ -150,11 +136,6 @@ public class ByteArray
return result;
}
public int getWriterIndex()
{
return writerIndex;
}
public void setWriterIndex(int writerIndex)
{
this.writerIndex = writerIndex;
@ -985,10 +966,15 @@ public class ByteArray
public void writeString(String value)
{
byte[] bytes = (byte[]) UNSAFE.getReference(value, STRING_VALUE_FIELD_OFFSET);
byte coder = UNSAFE.getByte(value, STRING_CODER_FIELD_OFFSET);
int idx = writerIndex;
int numBytes = bytes.length;
byte[] bytes = (byte[]) UNSAFE.getReference(value, STRING_VALUE_FIELD_OFFSET);
byte coder = UNSAFE.getByte(value, STRING_CODER_FIELD_OFFSET);
writeString(bytes, coder);
}
public void writeString(byte[] bytes, byte coder)
{
int idx = writerIndex;
int numBytes = bytes.length;
ensureNewWriterIndex(idx + 9 + numBytes);
UNSAFE.putByte(array, BYTE_ARRAY_OFFSET + idx, coder);
writerIndex = idx + 1;

View File

@ -36,10 +36,11 @@ public interface JfireSE
ClassInfo getOrCreateClassInfo(Class<?> clazz);
ClassInfo find(byte[] classNameBytes, int classId);
ClassInfo find(String className, int classId);
ClassInfo find(int classId);
Object readByUnderInstanceIdFlag(ByteArray byteArray, byte flag);
void addCleanClassInfo(ClassInfo classInfo);
}

View File

@ -6,8 +6,9 @@ import com.jfirer.se2.classinfo.StaticClasInfo;
import com.jfirer.se2.serializer.Serializer;
import com.jfirer.se2.serializer.SerializerFactory;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JfireSEImpl implements JfireSE
@ -28,7 +29,8 @@ public class JfireSEImpl implements JfireSE
private Map<Class<?>, ClassInfo> classInfoMap = new HashMap<>();
private SerializerFactory serializerFactory = new SerializerFactory(this);
private Map<byte[], ClassInfo> classInfoCacheMap = new HashMap<>();
private Map<byte[], Class<?>> classNameBytesCache = new HashMap<>();
private Map<String, Class<?>> classNameBytesCache = new HashMap<>();
private List<ClassInfo> cleanClassInfos = new ArrayList<>();
public JfireSEImpl(boolean refTracking, StaticClasInfo[] staticClasInfos)
{
@ -42,6 +44,7 @@ public class JfireSEImpl implements JfireSE
for (StaticClasInfo each : staticClasInfos)
{
each.setSerializer(serializerFactory.getSerializer(each.getClazz()));
each.setJfireSE(this);
}
}
@ -65,6 +68,7 @@ public class JfireSEImpl implements JfireSE
serializedClassInfos = tmp;
}
DynamicClassInfo dynamicClassInfo = new DynamicClassInfo((short) dynamicClassId, clazz, refTracking);
dynamicClassInfo.setJfireSE(this);
serializedClassInfos[dynamicClassId] = dynamicClassInfo;
dynamicClassId++;
Serializer serializer = serializerFactory.getSerializer(clazz);
@ -75,15 +79,17 @@ public class JfireSEImpl implements JfireSE
private void resetSerialized()
{
for (int i = staticClassId; i < dynamicClassId; i++)
for (ClassInfo each : cleanClassInfos)
{
serializedClassInfos[i].reset();
each.reset();
}
cleanClassInfos.clear();
}
@Override
public byte[] serialize(Object instance)
{
byteArray.clear();
if (instance == null)
{
byteArray.put(JfireSE.NULL);
@ -94,7 +100,6 @@ public class JfireSEImpl implements JfireSE
ClassInfo classInfo = getOrCreateClassInfo(instance.getClass());
classInfo.write(byteArray, instance);
byte[] array = byteArray.toArray();
byteArray.clear();
resetSerialized();
return array;
}
@ -102,57 +107,62 @@ public class JfireSEImpl implements JfireSE
@Override
public Object deSerialize(byte[] bytes)
{
ByteArray stream = new ByteArray(bytes);
byte b = stream.get();
byteArray.resetFor(bytes);
byte b = byteArray.get();
if (b == JfireSE.NULL)
{
return null;
}
switch (b)
Object result = switch (b)
{
case JfireSE.NAME_ID_CONTENT_TRACK ->
{
byte[] classNameBytes = stream.readBytesWithSizeEmbedded();
int classId = stream.readPositiveVarInt();
ClassInfo classInfo = find(classNameBytes, classId);
return classInfo.readWithTrack(stream);
String name = byteArray.readString();
int classId = byteArray.readPositiveVarInt();
ClassInfo classInfo = find(name, classId);
yield classInfo.readWithTrack(byteArray);
}
case JfireSE.NAME_ID_CONTENT_UN_TRACK ->
{
byte[] classNameBytes = stream.readBytesWithSizeEmbedded();
int classId = stream.readPositiveVarInt();
return find(classNameBytes, classId).readWithoutTrack(stream);
String className = byteArray.readString();
int classId = byteArray.readPositiveVarInt();
yield find(className, classId).readWithoutTrack(byteArray);
}
case JfireSE.ID_CONTENT_TRACK ->
{
int classId = stream.readPositiveVarInt();
int classId = byteArray.readPositiveVarInt();
ClassInfo classInfo = find(classId);
return classInfo.readWithTrack(stream);
yield classInfo.readWithTrack(byteArray);
}
case JfireSE.ID_CONTENT_UN_TRACK ->
{
int classId = stream.readPositiveVarInt();
int classId = byteArray.readPositiveVarInt();
ClassInfo classInfo = find(classId);
return classInfo.readWithoutTrack(stream);
yield classInfo.readWithoutTrack(byteArray);
}
default -> throw new RuntimeException("未知的序列化类型");
}
};
resetSerialized();
return result;
}
@Override
public ClassInfo find(byte[] classNameBytes, int classId)
public ClassInfo find(String className, int classId)
{
Class<?> clazz = classNameBytesCache.computeIfAbsent(classNameBytes, bytes -> {
Class<?> aClass = classNameBytesCache.get(className);
if (aClass == null)
{
try
{
return Class.forName(new String(classNameBytes, StandardCharsets.UTF_8));
aClass = Class.forName(className);
}
catch (ClassNotFoundException e)
{
throw new RuntimeException(e);
}
});
ClassInfo classInfo = getOrCreateClassInfo(clazz);
classNameBytesCache.put(className, aClass);
}
ClassInfo classInfo = getOrCreateClassInfo(aClass);
if (deSerializedClassInfos == null)
{
deSerializedClassInfos = new ClassInfo[classId + 1];
@ -187,16 +197,16 @@ public class JfireSEImpl implements JfireSE
{
case JfireSE.NAME_ID_CONTENT_TRACK ->
{
byte[] classNameBytes = byteArray.readBytesWithSizeEmbedded();
int classId = byteArray.readPositiveVarInt();
ClassInfo classInfo = find(classNameBytes, classId);
String className = byteArray.readString();
int classId = byteArray.readPositiveVarInt();
ClassInfo classInfo = find(className, classId);
return refTracking ? classInfo.readWithTrack(byteArray) : classInfo.readWithoutTrack(byteArray);
}
case JfireSE.NAME_ID_CONTENT_UN_TRACK ->
{
byte[] classNameBytes = byteArray.readBytesWithSizeEmbedded();
int classId = byteArray.readPositiveVarInt();
return find(classNameBytes, classId).readWithoutTrack(byteArray);
String className = byteArray.readString();
int classId = byteArray.readPositiveVarInt();
return find(className, classId).readWithoutTrack(byteArray);
}
case JfireSE.ID_INSTANCE_ID ->
{
@ -215,4 +225,10 @@ public class JfireSEImpl implements JfireSE
default -> throw new RuntimeException("未知的序列化类型");
}
}
@Override
public void addCleanClassInfo(ClassInfo classInfo)
{
cleanClassInfos.add(classInfo);
}
}

View File

@ -7,26 +7,31 @@ import io.github.karlatemp.unsafeaccessor.Unsafe;
import lombok.Data;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@Data
public abstract class ClassInfo implements RefTracking
{
protected final short classId;
protected final byte[] classNameBytes;
protected final byte[] classNameStringBytes;
protected final byte classNameStringCoder;
protected final Class<?> clazz;
protected final boolean refTrack;
protected Object[] tracking;
protected int refTrackingIndex = 0;
protected Serializer serializer;
protected JfireSE jfireSE;
protected boolean firstSerialized = true;
protected static final Unsafe UNSAFE = Unsafe.getUnsafe();
public ClassInfo(short classId, Class<?> clazz, boolean refTrack)
{
this.classId = classId;
this.clazz = clazz;
this.refTrack = refTrack;
classNameBytes = clazz.getName().getBytes(StandardCharsets.UTF_8);
this.classId = classId;
this.clazz = clazz;
this.refTrack = refTrack;
classNameBytes = clazz.getName().getBytes(StandardCharsets.UTF_8);
classNameStringBytes = (byte[]) UNSAFE.getReference(clazz.getName(), ByteArray.STRING_VALUE_FIELD_OFFSET);
classNameStringCoder = UNSAFE.getByte(clazz.getName(), ByteArray.STRING_CODER_FIELD_OFFSET);
}
@Override
@ -57,9 +62,13 @@ public abstract class ClassInfo implements RefTracking
{
if (refTrackingIndex != 0)
{
Arrays.fill(tracking, 0, refTrackingIndex, null);
for (int i = 0; i < refTrackingIndex; i++)
{
tracking[i] = null;
}
refTrackingIndex = 0;
}
firstSerialized = true;
}
/**
@ -108,7 +117,13 @@ public abstract class ClassInfo implements RefTracking
*/
public Object readWithTrack(ByteArray byteArray)
{
return serializer.read(byteArray, this);
Object result = serializer.read(byteArray, this);
if (firstSerialized)
{
firstSerialized = false;
jfireSE.addCleanClassInfo(this);
}
return result;
}
/**

View File

@ -14,35 +14,30 @@ public class DynamicClassInfo extends ClassInfo
/**
*
*/
private boolean firstSerialized = true;
public DynamicClassInfo(short classId, Class<?> clazz, boolean refTracking)
{
super(classId, clazz, refTracking);
}
@Override
public void reset()
{
super.reset();
firstSerialized = true;
}
@Override
public void write(ByteArray byteArray, Object instance)
{
if (refTrack)
if (firstSerialized)
{
if (firstSerialized)
if (refTrack)
{
firstSerialized = false;
addTracking(instance);
byteArray.put(JfireSE.NAME_ID_CONTENT_TRACK);
byteArray.writeBytesWithSizeEmbedded(classNameBytes);
byteArray.writePositiveVarInt(classId);
serializer.writeBytes(byteArray, instance);
}
else
firstSerialized = false;
byteArray.put(JfireSE.NAME_ID_CONTENT_TRACK);
byteArray.writeString(classNameStringBytes, classNameStringCoder);
byteArray.writePositiveVarInt(classId);
serializer.writeBytes(byteArray, instance);
jfireSE.addCleanClassInfo(this);
}
else
{
if (refTrack)
{
int i = addTracking(instance);
if (i == -1)
@ -58,17 +53,6 @@ public class DynamicClassInfo extends ClassInfo
byteArray.writePositiveVarInt(i);
}
}
}
else
{
if (firstSerialized)
{
firstSerialized = false;
byteArray.put(JfireSE.NAME_ID_CONTENT_UN_TRACK);
byteArray.writeBytesWithSizeEmbedded(classNameBytes);
byteArray.writePositiveVarInt(classId);
serializer.writeBytes(byteArray, instance);
}
else
{
byteArray.put(JfireSE.ID_CONTENT_UN_TRACK);

View File

@ -28,6 +28,11 @@ public class StaticClasInfo extends ClassInfo
byteArray.writePositiveVarInt(classId);
byteArray.writePositiveVarInt(i);
}
if (firstSerialized)
{
firstSerialized = false;
jfireSE.addCleanClassInfo(this);
}
}
else
{

View File

@ -0,0 +1,49 @@
package org.example;
import com.jfirer.se2.JfireSE;
import org.apache.fury.Fury;
import org.apache.fury.config.Language;
import org.example.sm.TestDataSm;
import org.example.sm2.TestDataSm2;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 2, time = 3)
@Measurement(iterations = 3, time = 3)
@OutputTimeUnit(TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class BenchMarkRead
{
Fury fury = Fury.builder().withLanguage(Language.JAVA)//
.requireClassRegistration(false)//
.withRefTracking(true).build();
JfireSE jfireSE = JfireSE.supportRefTracking(true).build();
TestData data = new TestData().setTestDataSm(new TestDataSm()).setTestDataSm2(new TestDataSm2());
byte[] serialize = jfireSE.serialize(data);
byte[] serialize2 = fury.serialize(data);
@Benchmark
public void testJfireSERead()
{
jfireSE.deSerialize(serialize);
}
@Benchmark
public void testFuryRead()
{
fury.deserialize(serialize2);
}
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder().include(BenchMarkRead.class.getSimpleName()).build();
new Runner(opt).run();
}
}

View File

@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit;
@OutputTimeUnit(TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Benchmark)
public class BenchMark
public class BenchMarkWrite
{
Fse fse = new Fse();
Fse fse_3 = new Fse().useCompile();
@ -32,7 +32,6 @@ public class BenchMark
.withRefTracking(true).build();
JfireSE jfireSE = JfireSE.supportRefTracking(true).build();
@Benchmark
public void testFSENoCompile()
{
buf.clear();
@ -45,7 +44,6 @@ public class BenchMark
byte[] bytes = fury.serialize(data);
}
@Benchmark
public void testFSEDirectCompile()
{
buf.clear();
@ -60,7 +58,7 @@ public class BenchMark
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder().include(BenchMark.class.getSimpleName()).build();
Options opt = new OptionsBuilder().include(BenchMarkWrite.class.getSimpleName()).build();
new Runner(opt).run();
}
}

View File

@ -0,0 +1,22 @@
package org.example;
import com.jfirer.se2.JfireSE;
import org.example.sm.TestDataSm;
import org.example.sm2.TestDataSm2;
import org.junit.Test;
public class Profile
{
JfireSE jfireSE = JfireSE.supportRefTracking(true).build();
TestData data = new TestData().setTestDataSm(new TestDataSm()).setTestDataSm2(new TestDataSm2());
byte[] serialize = jfireSE.serialize(data);
@Test
public void test()
{
for (int i = 0; i < 10000000; i++)
{
jfireSE.deSerialize(serialize);
}
}
}

View File

@ -1,48 +0,0 @@
package org.example.festest;
import com.jfirer.fse.ByteArray;
import com.jfirer.se2.JfireSE;
import org.apache.fury.Fury;
import org.apache.fury.config.Language;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.util.concurrent.TimeUnit;
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 3, time = 3)
@Threads(1)
@Fork(2)
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class BenchMark
{
JfireSE jfireSE = JfireSE.supportRefTracking(true).build();
TestData data = new TestData();
ByteArray buf = ByteArray.allocate(100);
Fury fury = Fury.builder().withLanguage(Language.JAVA)//
.requireClassRegistration(false)//
.withRefTracking(true).build();
@Benchmark
public void testFury()
{
byte[] bytes = fury.serialize(data);
}
@Benchmark
public void testJfireSE()
{
jfireSE.serialize(data);
}
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder().include(BenchMark.class.getSimpleName()).build();
new Runner(opt).run();
}
}