Skip to content

Commit

Permalink
Rename to TypeInfo (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
agocke authored Jun 4, 2024
1 parent c949a9a commit 7e220ef
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 58 deletions.
38 changes: 19 additions & 19 deletions perf/bench/SampleTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,16 @@ public partial record Location

public partial record LocationWrap : IDeserialize<Location>
{
private static readonly FieldMap s_fieldMap = new(nameof(LocationWrap), [
"id",
"address1",
"address2",
"city",
"state",
"postalCode",
"name",
"phoneNumber",
"country"
private static readonly TypeInfo s_fieldMap = TypeInfo.Create<Location>(nameof(Location), [
("id", typeof(Location).GetProperty("Id")!),
("address1", typeof(Location).GetProperty("Address1")!),
("address2", typeof(Location).GetProperty("Address2")!),
("city", typeof(Location).GetProperty("City")!),
("state", typeof(Location).GetProperty("State")!),
("postalCode", typeof(Location).GetProperty("PostalCode")!),
("name", typeof(Location).GetProperty("Name")!),
("phoneNumber", typeof(Location).GetProperty("PhoneNumber")!),
("country", typeof(Location).GetProperty("Country")!)
]);

static Benchmarks.Location Serde.IDeserialize<Benchmarks.Location>.Deserialize(IDeserializer deserializer)
Expand All @@ -90,39 +90,39 @@ public partial record LocationWrap : IDeserialize<Location>
switch (index)
{
case 0:
_l_id = typeDeserialize.ReadValue<int, Int32Wrap>();
_l_id = typeDeserialize.ReadValue<int, Int32Wrap>(index);
_r_assignedValid |= ((ushort)1) << 0;
break;
case 1:
_l_address1 = typeDeserialize.ReadValue<string, StringWrap>();
_l_address1 = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 1;
break;
case 2:
_l_address2 = typeDeserialize.ReadValue<string, StringWrap>();
_l_address2 = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 2;
break;
case 3:
_l_city = typeDeserialize.ReadValue<string, StringWrap>();
_l_city = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 3;
break;
case 4:
_l_state = typeDeserialize.ReadValue<string, StringWrap>();
_l_state = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 4;
break;
case 5:
_l_postalcode = typeDeserialize.ReadValue<string, StringWrap>();
_l_postalcode = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 5;
break;
case 6:
_l_name = typeDeserialize.ReadValue<string, StringWrap>();
_l_name = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 6;
break;
case 7:
_l_phonenumber = typeDeserialize.ReadValue<string, StringWrap>();
_l_phonenumber = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 7;
break;
case 8:
_l_country = typeDeserialize.ReadValue<string, StringWrap>();
_l_country = typeDeserialize.ReadValue<string, StringWrap>(index);
_r_assignedValid |= ((ushort)1) << 8;
break;
}
Expand Down
92 changes: 64 additions & 28 deletions src/serde/IDeserialize.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
Expand Down Expand Up @@ -78,63 +80,97 @@ public interface IDeserializeType
public const int EndOfType = -1;
public const int IndexNotFound = -2;

int TryReadIndex(FieldMap map);
int TryReadIndex(TypeInfo map);

V ReadValue<V, D>() where D : IDeserialize<V>;
V ReadValue<V, D>(int index) where D : IDeserialize<V>;
}

/// <summary>
/// A map from field names to int indices. This is an optimization for deserializing types
/// that avoids allocating strings for field names.
/// TypeInfo holds a variety of indexed information about a type. The most important is a map
/// from field names to int indices. This is an optimization for deserializing types that avoids
/// allocating strings for field names.
///
/// It can also be used to get the custom attributes for a field.
/// </summary>
public sealed class FieldMap
public sealed class TypeInfo
{
#region Fields and auto-props

public string TypeName { get; }
private readonly ImmutableArray<(byte[] Utf8String, int Index)> _fieldNames;
// The field names are sorted by the Utf8 representation of the field name.
private readonly ImmutableArray<(ReadOnlyMemory<byte> Utf8Name, int Index)> _nameToIndex;
private readonly ImmutableArray<FieldInfo> _indexToInfo;

private TypeInfo(
string typeName,
ImmutableArray<(ReadOnlyMemory<byte>, int)> nameToIndex,
ImmutableArray<FieldInfo> indexToInfo)
{
TypeName = typeName;
_nameToIndex = nameToIndex;
_indexToInfo = indexToInfo;
}

#endregion
private readonly record struct FieldInfo(IList<CustomAttributeData> CustomAttributesData);


private static readonly UTF8Encoding s_utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

/// <summary>
/// Create a new field mapping. The ordering of the field names is important -- it
/// Create a new field mapping. The ordering of the fields is important -- it
/// corresponds to the index returned by <see cref="IDeserializeType.TryReadIndex" />.
/// </summary>
public FieldMap(
string typeName,
ReadOnlySpan<string> fieldNames)
public static TypeInfo Create<T>(
string serializeTypeName,
ReadOnlySpan<(string SerializeName, MemberInfo MemberInfo)> fields)
{
TypeName = typeName;

var builder = ImmutableArray.CreateBuilder<(byte[] Utf8String, int Index)>(fieldNames.Length);
for (int index = 0; index < fieldNames.Length; index++)
var type = typeof(T);
var nameToIndexBuilder = ImmutableArray.CreateBuilder<(ReadOnlyMemory<byte> Utf8Name, int Index)>(fields.Length);
var indexToInfoBuilder = ImmutableArray.CreateBuilder<FieldInfo>(fields.Length);
for (int index = 0; index < fields.Length; index++)
{
builder.Add((s_utf8.GetBytes(fieldNames[index]), index));
var (serializeName, memberInfo) = fields[index];
if (memberInfo is null)
{
throw new ArgumentNullException(serializeName);
}

nameToIndexBuilder.Add((s_utf8.GetBytes(serializeName), index));
var fieldInfo = new FieldInfo(memberInfo.GetCustomAttributesData());
indexToInfoBuilder.Add(fieldInfo);
}
builder.Sort((left, right) =>
left.Utf8String.AsSpan().SequenceCompareTo(right.Utf8String.AsSpan()));

_fieldNames = builder.MoveToImmutable();
nameToIndexBuilder.Sort((left, right) =>
left.Utf8Name.Span.SequenceCompareTo(right.Utf8Name.Span));

return new TypeInfo(serializeTypeName, nameToIndexBuilder.ToImmutable(), indexToInfoBuilder.ToImmutable());
}

public int TryReadIndex(Utf8Span utf8FieldName)
/// <summary>
/// Returns an index corresponding to the location of the field in the original
/// ReadOnlySpan passed during creation of the <see cref="TypeInfo"/>. This can be
/// used as a fast lookup for a field based on its UTF-8 name.
/// </summary>
public int TryGetIndex(Utf8Span utf8FieldName)
{
int mapIndex = BinarySearch(_fieldNames.AsSpan(), utf8FieldName);
int mapIndex = BinarySearch(_nameToIndex.AsSpan(), utf8FieldName);

return mapIndex < 0 ? IDeserializeType.IndexNotFound : _fieldNames[mapIndex].Index;
return mapIndex < 0 ? IDeserializeType.IndexNotFound : _nameToIndex[mapIndex].Index;
}

public IList<CustomAttributeData> GetCustomAttributeData(int index)
{
return _indexToInfo[index].CustomAttributesData;
}


[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int BinarySearch(ReadOnlySpan<(byte[] Utf8String, int Index)> span, Utf8Span fieldName)
private static int BinarySearch(ReadOnlySpan<(ReadOnlyMemory<byte>, int)> span, Utf8Span fieldName)
{
return BinarySearch(ref MemoryMarshal.GetReference(span), span.Length, fieldName);
}

// This is a copy of the BinarySearch method from System.MemoryExtensions.
// We can't use that version because ref structs can't yet be substituted for type arguments.
private static int BinarySearch(ref (byte[] Utf8String, int Index) spanStart, int length, Utf8Span fieldName)
private static int BinarySearch(ref (ReadOnlyMemory<byte> Utf8Name, int) spanStart, int length, Utf8Span fieldName)
{
int lo = 0;
int hi = length - 1;
Expand All @@ -149,7 +185,7 @@ private static int BinarySearch(ref (byte[] Utf8String, int Index) spanStart, in
// `int i = lo + ((hi - lo) >> 1);`
int i = (int)(((uint)hi + (uint)lo) >> 1);

int c = fieldName.SequenceCompareTo(Unsafe.Add(ref spanStart, i).Utf8String);
int c = fieldName.SequenceCompareTo(Unsafe.Add(ref spanStart, i).Utf8Name.Span);
if (c == 0)
{
return i;
Expand Down Expand Up @@ -193,6 +229,6 @@ public interface IDeserializer
T DeserializeEnumerable<T>(IDeserializeVisitor<T> v);
T DeserializeDictionary<T>(IDeserializeVisitor<T> v);
T DeserializeNullableRef<T>(IDeserializeVisitor<T> v);
IDeserializeType DeserializeType(FieldMap fieldMap);
IDeserializeType DeserializeType(TypeInfo typeInfo) => throw new NotImplementedException();
}
}
8 changes: 4 additions & 4 deletions src/serde/json/JsonDeserializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ public T DeserializeType<T>(string typeName, ReadOnlySpan<string> fieldNames, ID
return DeserializeDictionary(v);
}

public IDeserializeType DeserializeType(FieldMap fieldMap)
public IDeserializeType DeserializeType(TypeInfo fieldMap)
{
ref var reader = ref GetReader();
reader.ReadOrThrow();
Expand Down Expand Up @@ -299,12 +299,12 @@ public T DeserializeNullableRef<T>(IDeserializeVisitor<T> v)

partial class JsonDeserializer : IDeserializeType
{
V IDeserializeType.ReadValue<V, D>()
V IDeserializeType.ReadValue<V, D>(int index)
{
return D.Deserialize(this);
}

int IDeserializeType.TryReadIndex(FieldMap map)
int IDeserializeType.TryReadIndex(TypeInfo map)
{
ref var reader = ref GetReader();
reader.ReadOrThrow();
Expand All @@ -324,7 +324,7 @@ int IDeserializeType.TryReadIndex(FieldMap map)
{
span = reader.ValueSpan;
}
return map.TryReadIndex(span);
return map.TryGetIndex(span);
}
}

Expand Down
14 changes: 7 additions & 7 deletions test/Serde.Test/CustomImplTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ private sealed partial record RgbWithFieldMap : IDeserialize<RgbWithFieldMap>
{
public int Red, Green, Blue;

private static readonly FieldMap s_fieldMap = new(nameof(RgbWithFieldMap), [
"red",
"green",
"blue"
private static readonly TypeInfo s_fieldMap = TypeInfo.Create<RgbWithFieldMap>(nameof(RgbWithFieldMap), [
("red", typeof(RgbWithFieldMap).GetField("Red")!),
("green", typeof(RgbWithFieldMap).GetField("Green")!),
("blue", typeof(RgbWithFieldMap).GetField("Blue")!)
]);

static RgbWithFieldMap IDeserialize<RgbWithFieldMap>.Deserialize(IDeserializer deserializer)
Expand All @@ -31,13 +31,13 @@ static RgbWithFieldMap IDeserialize<RgbWithFieldMap>.Deserialize(IDeserializer d
switch (index)
{
case 0:
red = deType.ReadValue<int, Int32Wrap>();
red = deType.ReadValue<int, Int32Wrap>(index);
break;
case 1:
green = deType.ReadValue<int, Int32Wrap>();
green = deType.ReadValue<int, Int32Wrap>(index);
break;
case 2:
blue = deType.ReadValue<int, Int32Wrap>();
blue = deType.ReadValue<int, Int32Wrap>(index);
break;
}
}
Expand Down

0 comments on commit 7e220ef

Please sign in to comment.