Skip to content

Commit

Permalink
- Remove closure allocations from Record (#137)
Browse files Browse the repository at this point in the history
- Initialize lists by a capacity instead of resizing
- Introduce helper method to read alias instead of allocating new ReadOnlyCollection on each access

Co-authored-by: Manvel Ghazaryan <[email protected]>
  • Loading branch information
gmanvel and Manvel Ghazaryan authored Nov 16, 2023
1 parent 7de9797 commit 9021787
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 34 deletions.
34 changes: 18 additions & 16 deletions src/AvroConvert/AvroObjectServices/Read/Resolvers/Array.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using SolTechnology.Avro.AvroObjectServices.Schemas;
using SolTechnology.Avro.AvroObjectServices.Schemas.Abstract;
using SolTechnology.Avro.Infrastructure.Extensions;
Expand All @@ -33,9 +34,9 @@ namespace SolTechnology.Avro.AvroObjectServices.Read
{
internal partial class Resolver
{
private readonly Dictionary<int, Func<IList>> _cachedArrayInitializers = new Dictionary<int, Func<IList>>();
private readonly Dictionary<int, Func<IList>> _cachedArrayInitializers = new();

internal object ResolveArray(TypeSchema writerSchema, TypeSchema readerSchema, IReader d, Type type, long itemsCount = 0)
internal object ResolveArray(TypeSchema writerSchema, TypeSchema readerSchema, IReader reader, Type type, long itemsCount = 0)
{
if (writerSchema.Type == AvroType.Array)
{
Expand All @@ -45,52 +46,55 @@ internal object ResolveArray(TypeSchema writerSchema, TypeSchema readerSchema, I

if (type.IsDictionary())
{
return ResolveDictionary((RecordSchema)writerSchema, (RecordSchema)readerSchema, d, type);
return ResolveDictionary((RecordSchema)writerSchema, (RecordSchema)readerSchema, reader, type);
}

var containingType = type.GetEnumeratedType();
var typeHash = type.GetHashCode();

Func<IList> resultFunc;
if (_cachedArrayInitializers.ContainsKey(typeHash))
int capacity = itemsCount == 0 ? (int)reader.ReadArrayStart() : (int)itemsCount;
Func <IList> resultFunc;
if (_cachedArrayInitializers.TryGetValue(typeHash, out var initializer))
{
resultFunc = _cachedArrayInitializers[typeHash];
resultFunc = initializer;
}
else
{
var resultType = typeof(List<>).MakeGenericType(containingType);
resultFunc = Expression.Lambda<Func<IList>>(Expression.New(resultType)).Compile();
ConstructorInfo constructor = resultType.GetConstructor(new[] { typeof(int) });
ConstantExpression capacityExpr = Expression.Constant(capacity);
NewExpression newExp = Expression.New(constructor, capacityExpr);
resultFunc = Expression.Lambda<Func<IList>>(newExp).Compile();
_cachedArrayInitializers.Add(typeHash, resultFunc);
}
IList result = resultFunc.Invoke();


int i = 0;
if (itemsCount == 0)
{
for (int n = (int)d.ReadArrayStart(); n != 0; n = (int)d.ReadArrayNext())
for (int n = capacity; n != 0; n = (int)reader.ReadArrayNext())
{
for (int j = 0; j < n; j++, i++)
{
dynamic y = Resolve(writerSchema, readerSchema, d, containingType);
result.Add(y);
object item = Resolve(writerSchema, readerSchema, reader, containingType);
result.Add(item);
}
}
}
else
{
for (int k = 0; k < itemsCount; k++)
{
result.Add(Resolve(writerSchema, readerSchema, d, containingType));
result.Add(Resolve(writerSchema, readerSchema, reader, containingType));
}
}

if (type.IsArray)
{
var containingTypeArray = containingType.MakeArrayType();

dynamic resultArray = Activator.CreateInstance(containingTypeArray, new object[] { result.Count });
result.CopyTo(resultArray, 0);
Array resultArray = (Array)Activator.CreateInstance(containingTypeArray, result.Count);
result.CopyTo(resultArray!, 0);
return resultArray;
}

Expand All @@ -99,7 +103,6 @@ internal object ResolveArray(TypeSchema writerSchema, TypeSchema readerSchema, I
return result;
}


var hashSetType = typeof(HashSet<>).MakeGenericType(containingType);
if (type == hashSetType)
{
Expand All @@ -112,7 +115,6 @@ internal object ResolveArray(TypeSchema writerSchema, TypeSchema readerSchema, I
return resultHashSet;
}


var reflectionResult = type.GetField("Empty")?.GetValue(null);
var addMethod = type.GetMethod("Add");
foreach (dynamic item in result)
Expand Down
56 changes: 38 additions & 18 deletions src/AvroConvert/AvroObjectServices/Read/Resolvers/Record.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using FastMember;
using SolTechnology.Avro.AvroObjectServices.Schemas;
Expand All @@ -28,8 +27,8 @@ namespace SolTechnology.Avro.AvroObjectServices.Read
{
internal partial class Resolver
{
private readonly Dictionary<int, Dictionary<string, Func<object>>> _readStepsDictionary = new Dictionary<int, Dictionary<string, Func<object>>>();
private readonly Dictionary<int, TypeAccessor> _accessorDictionary = new Dictionary<int, TypeAccessor>();
private readonly Dictionary<int, Dictionary<string, ReadStep>> _readStepsDictionary = new();
private readonly Dictionary<int, TypeAccessor> _accessorDictionary = new();

protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema readerSchema, IReader dec,
Type type)
Expand All @@ -40,36 +39,28 @@ protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema r
var typeHash = type.GetHashCode();

TypeAccessor accessor;
Dictionary<string, Func<object>> readSteps;
Dictionary<string, ReadStep> readSteps;

if (!_accessorDictionary.ContainsKey(typeHash))
{
accessor = TypeAccessor.Create(type, true);
readSteps = new Dictionary<string, Func<object>>();

readSteps = new Dictionary<string, ReadStep>();
foreach (RecordFieldSchema wf in writerSchema.Fields)
{
if (readerSchema.TryGetField(wf.Name, out var rf))
{
string name = rf.Aliases.FirstOrDefault() ?? wf.Name;
string name = rf.GetAliasOrDefault() ?? wf.Name;

var members = accessor.GetMembers();
var memberInfo = members.FirstOrDefault(n =>
n.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
var memberInfo = members.FirstOrDefault(n => n.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase));
if (memberInfo == null)
{
continue;
}

Func<object> func = () =>
{
object value = Resolve(wf.TypeSchema, rf.TypeSchema, dec, memberInfo.Type);
return value ?? FormatDefaultValue(wf.DefaultValue, memberInfo);
};

accessor[result, memberInfo.Name] = func.Invoke();
accessor[result, memberInfo.Name] = GetValue(wf, rf, memberInfo, dec);

readSteps.Add(memberInfo.Name, func);
readSteps.Add(memberInfo.Name, new ReadStep(wf, rf, memberInfo));

}
else
Expand All @@ -86,7 +77,13 @@ protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema r

foreach (var readStep in readSteps)
{
accessor[result, readStep.Key] = readStep.Value.Invoke();
var readStepValue = readStep.Value;
accessor[result, readStep.Key] =
GetValue(
readStepValue.WriteFieldSchema,
readStepValue.ReadFieldSchema,
readStepValue.MemberInfo,
dec);
}
}

Expand Down Expand Up @@ -123,6 +120,15 @@ protected virtual object ResolveRecord(RecordSchema writerSchema, RecordSchema r
}
}

private object GetValue(RecordFieldSchema wf,
RecordFieldSchema rf,
Member memberInfo,
IReader dec)
{
object value = Resolve(wf.TypeSchema, rf.TypeSchema, dec, memberInfo.Type);
return value ?? FormatDefaultValue(wf.DefaultValue, memberInfo);
}

private static object FormatDefaultValue(object defaultValue, Member memberInfo)
{
if (defaultValue == null)
Expand Down Expand Up @@ -152,5 +158,19 @@ private static object FormatDefaultValue(object defaultValue, Member memberInfo)

return Convert.ChangeType(defaultValue, t);
}

private class ReadStep
{
public RecordFieldSchema WriteFieldSchema { get; set; }
public RecordFieldSchema ReadFieldSchema { get; set; }
public Member MemberInfo { get; set; }

public ReadStep(RecordFieldSchema writeFieldSchema, RecordFieldSchema readFieldSchema, Member memberInfo)
{
WriteFieldSchema = writeFieldSchema;
ReadFieldSchema = readFieldSchema;
MemberInfo = memberInfo;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json;
using SolTechnology.Avro.AvroObjectServices.BuildSchema;
Expand All @@ -39,6 +40,7 @@ internal sealed class RecordFieldSchema : Schema
internal int Position { get; }
internal NamedEntityAttributes NamedEntityAttributes { get; }

internal string GetAliasOrDefault() => NamedEntityAttributes.Aliases.FirstOrDefault();

internal RecordFieldSchema(
NamedEntityAttributes namedEntityAttributes,
Expand Down

0 comments on commit 9021787

Please sign in to comment.