neo icon indicating copy to clipboard operation
neo copied to clipboard

Fast serialization

Open erikzhang opened this issue 5 years ago • 5 comments

Redesigned the object serialization scheme using reflection.

  1. Don't worry about performance, because all reflections will only be executed once. It will be read from the cache later.
  2. Object creation and property assignment use precompiled expression trees, which makes them almost the same speed as direct creation and assignment.
  3. We don't need to add the Serialize() and Deserialize() methods for each class, just let it implement the ISerializable interface and add the Serialized attribute for each property that needs to be serialized. The new serializer will automatically perform serialization and deserialization.

@neo-project/core What do you think? If you think it's good, I will complete it.

erikzhang avatar Aug 11 '20 19:08 erikzhang

Object creation benchmark:

class TestClassForConstructor
{
    private delegate TestClassForConstructor ConstructorDelegate(object arg);

    public TestClassForConstructor(object arg)
    {
    }

    public static void RunTest()
    {
        Type t = typeof(TestClassForConstructor);
        Type paramType = typeof(object);
        var constructorInfo = t.GetConstructor(new[] { paramType });
        var paramExpr = Expression.Parameter(paramType);
        var newExpr = Expression.New(constructorInfo, paramExpr);
        var constructorExpr = Expression.Lambda<ConstructorDelegate>(newExpr, paramExpr);
        var constructor = constructorExpr.Compile();

        byte[] memory = new byte[2];
        const int count = 100_000_000;

        Stopwatch stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < count; i++)
            _ = new TestClassForConstructor(memory);
        stopwatch.Stop();
        Console.WriteLine($"New object: t={stopwatch.ElapsedMilliseconds}ms");

        stopwatch.Restart();
        for (int i = 0; i < count; i++)
            _ = constructor(memory);
        stopwatch.Stop();
        Console.WriteLine($"Expression: t={stopwatch.ElapsedMilliseconds}ms");
    }
}

Result:

New object: t=923ms
Expression: t=1203ms

erikzhang avatar Aug 11 '20 19:08 erikzhang

Property assignment benchmark:

class TestClassForProperty
{
    public string MyProperty { get; set; }

    public static void RunTest()
    {
        TestClassForProperty obj = new TestClassForProperty();
        PropertyInfo property = obj.GetType().GetProperty(nameof(MyProperty));
        Action<string> setMethod = (Action<string>)property.SetMethod.CreateDelegate(typeof(Action<string>), obj);

        const string data = "Hello";
        const int count = 100_000_000;

        Stopwatch stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < count; i++)
            obj.MyProperty = data;
        stopwatch.Stop();
        Console.WriteLine($"Set property:\tt={stopwatch.ElapsedMilliseconds}ms");

        stopwatch.Restart();
        for (int i = 0; i < count; i++)
            setMethod(data);
        stopwatch.Stop();
        Console.WriteLine($"Call setMethod:\tt={stopwatch.ElapsedMilliseconds}ms");
    }
}

Result:

Set property:   t=563ms
Call setMethod: t=564ms

erikzhang avatar Aug 11 '20 19:08 erikzhang

Example:

public class Transaction : ISerializable
{
    [Serialized(0, Serializer = typeof(UnmanagedSerializer<byte>))]
    public byte Version { get; set; }
    [Serialized(1, Serializer = typeof(UnmanagedSerializer<uint>))]
    public uint Nonce { get; set; }
    [Serialized(2, Serializer = typeof(UnmanagedSerializer<long>))]
    public long Sysfee { get; set; }
    [Serialized(3, Serializer = typeof(UnmanagedSerializer<long>))]
    public long Netfee { get; set; }
    [Serialized(4, Serializer = typeof(UnmanagedSerializer<uint>))]
    public uint ValidUntilBlock { get; set; }
    [Serialized(5, Max = 16)]
    public Signer[] Signers { get; set; }
    [Serialized(6, Serializer = typeof(PolymorphousArraySerializer<TransactionAttribute, TransactionAttributeType>), Max = 16)]
    public TransactionAttribute[] Attributes { get; set; }
    [Serialized(7, Max = ushort.MaxValue)]
    public byte[] Script { get; set; }
    [Serialized(8, Max = 16)]
    public Witness[] Witnesses { get; set; }
}

erikzhang avatar Aug 11 '20 20:08 erikzhang

In my experience, reflection always it's slower, in your benchmarks you need to increase the time spend in get the structure from the cache, i think that it will be easier, but slower.

shargon avatar Aug 12 '20 08:08 shargon

In my experience, reflection always it's slower

It use reflection only once.

erikzhang avatar Aug 12 '20 08:08 erikzhang

Close as inactive

Jim8y avatar Feb 18 '24 06:02 Jim8y