Browse Source

Don't allocate strings for property key lookups

voice-allocs
RogueException 7 years ago
parent
commit
91b4fb3272
18 changed files with 119 additions and 67 deletions
  1. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs
  2. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs
  3. +1
    -1
      src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs
  4. +33
    -27
      src/Discord.Net.Serialization/ConverterCollection.cs
  5. +1
    -1
      src/Discord.Net.Serialization/Json/Converters/Collections.cs
  6. +1
    -1
      src/Discord.Net.Serialization/Json/Converters/Nullable.cs
  7. +4
    -4
      src/Discord.Net.Serialization/Json/Converters/Object.cs
  8. +2
    -2
      src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs
  9. +3
    -3
      src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs
  10. +3
    -3
      src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs
  11. +4
    -4
      src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs
  12. +1
    -1
      src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs
  13. +4
    -4
      src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs
  14. +3
    -1
      src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs
  15. +4
    -3
      src/Discord.Net.Serialization/ModelMap.cs
  16. +5
    -2
      src/Discord.Net.Serialization/PropertyMap.cs
  17. +6
    -8
      src/Discord.Net.Serialization/SerializationFormat.cs
  18. +42
    -0
      src/Discord.Net.Serialization/Utf8SpanComparer.cs

+ 1
- 1
src/Discord.Net.Rest/Serialization/JsonConverters/EntityOrIdPropertyConverter.cs View File

@@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters
else
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value.Id);
writer.WriteAttribute(map.Utf16Key, value.Id);
else
writer.WriteValue(value.Id);
}


+ 1
- 1
src/Discord.Net.Rest/Serialization/JsonConverters/Int53PropertyConverter.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value.ToString());
}


+ 1
- 1
src/Discord.Net.Rest/Serialization/JsonConverters/UInt53PropertyConverter.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value.ToString());
}


+ 33
- 27
src/Discord.Net.Serialization/ConverterCollection.cs View File

@@ -65,42 +65,48 @@ namespace Discord.Serialization
public object Get<TType>(PropertyInfo propInfo = null)
{
return _cache.GetOrAdd(typeof(TType), _ =>
if (!_cache.TryGetValue(typeof(TType), out var result))
{
TypeInfo typeInfo = typeof(TType).GetTypeInfo();
object converter = Create(typeof(TType), propInfo);
result = _cache.GetOrAdd(typeof(TType), converter);
}
return result;
}
private object Create(Type type, PropertyInfo propInfo)
{
TypeInfo typeInfo = type.GetTypeInfo();

//Mapped generic converters (List<T> -> CollectionPropertyConverter<T>)
if (typeInfo.IsGenericType)
//Mapped generic converters (List<T> -> CollectionPropertyConverter<T>)
if (typeInfo.IsGenericType)
{
var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _mappedGenericTypes, typeInfo, propInfo);
if (converterType != null)
{
var converterType = FindConverterType(typeInfo.GetGenericTypeDefinition(), _mappedGenericTypes, typeInfo, propInfo);
if (converterType != null)
{
var innerType = typeInfo.GenericTypeArguments[0];
converterType = converterType.MakeGenericType(innerType);
object innerConverter = GetInnerConverter(innerType, propInfo);
return Activator.CreateInstance(converterType, innerConverter);
}
var innerType = typeInfo.GenericTypeArguments[0];
converterType = converterType.MakeGenericType(innerType);
object innerConverter = GetInnerConverter(innerType, propInfo);
return Activator.CreateInstance(converterType, innerConverter);
}
}

//Normal converters (bool -> BooleanPropertyConverter)
{
var converterType = FindConverterType(typeof(TType), _types, typeInfo, propInfo);
if (converterType != null)
return Activator.CreateInstance(converterType);
}
//Normal converters (bool -> BooleanPropertyConverter)
{
var converterType = FindConverterType(type, _types, typeInfo, propInfo);
if (converterType != null)
return Activator.CreateInstance(converterType);
}

//Generic converters (Model -> ObjectPropertyConverter<Model>)
//Generic converters (Model -> ObjectPropertyConverter<Model>)
{
var converterType = FindConverterType(_genericTypes, typeInfo, propInfo);
if (converterType != null)
{
var converterType = FindConverterType(_genericTypes, typeInfo, propInfo);
if (converterType != null)
{
converterType = converterType.MakeGenericType(typeof(TType));
return Activator.CreateInstance(converterType);
}
converterType = converterType.MakeGenericType(type);
return Activator.CreateInstance(converterType);
}
}

throw new InvalidOperationException($"Unsupported model type: {typeof(TType).Name}");
});
throw new InvalidOperationException($"Unsupported model type: {type.Name}");
}
private object GetInnerConverter(Type type, PropertyInfo propInfo)
=> _getConverterMethod.MakeGenericMethod(type).Invoke(this, new object[] { propInfo });


+ 1
- 1
src/Discord.Net.Serialization/Json/Converters/Collections.cs View File

@@ -26,7 +26,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, List<T> value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteArrayStart(map.Key);
writer.WriteArrayStart(map.Utf16Key);
else
writer.WriteArrayStart();
for (int i = 0; i < value.Count; i++)


+ 1
- 1
src/Discord.Net.Serialization/Json/Converters/Nullable.cs View File

@@ -28,7 +28,7 @@ namespace Discord.Serialization.Json.Converters
else
{
if (isTopLevel)
writer.WriteAttributeNull(map.Key);
writer.WriteAttributeNull(map.Utf16Key);
else
writer.WriteNull();
}


+ 4
- 4
src/Discord.Net.Serialization/Json/Converters/Object.cs View File

@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Text.Utf8;

namespace Discord.Serialization.Json.Converters
{
@@ -19,9 +20,8 @@ namespace Discord.Serialization.Json.Converters
return model;
if (reader.TokenType != JsonTokenType.PropertyName)
throw new SerializationException("Bad input, expected PropertyName");

string key = reader.ParseString();
if (_map.PropertiesByKey.TryGetValue(key, out var property))
if (_map.PropertiesByKey.TryGetValue(reader.Value, out var property))
(property as IJsonPropertyMap<T>).Read(model, ref reader);
else
reader.Skip(); //Unknown property, skip
@@ -31,7 +31,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, T value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteObjectStart(map.Key);
writer.WriteObjectStart(map.Utf16Key);
else
writer.WriteObjectStart();
for (int i = 0; i < _map.Properties.Length; i++)


+ 2
- 2
src/Discord.Net.Serialization/Json/Converters/Primitives.DateTime.cs View File

@@ -16,7 +16,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, DateTime value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -35,7 +35,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, DateTimeOffset value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}


+ 3
- 3
src/Discord.Net.Serialization/Json/Converters/Primitives.Float.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, float value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value.ToString());
writer.WriteAttribute(map.Utf16Key, value.ToString());
else
writer.WriteValue(value.ToString());
}
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, double value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value.ToString());
writer.WriteAttribute(map.Utf16Key, value.ToString());
else
writer.WriteValue(value.ToString());
}
@@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, decimal value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value.ToString());
writer.WriteAttribute(map.Utf16Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


+ 3
- 3
src/Discord.Net.Serialization/Json/Converters/Primitives.Other.cs View File

@@ -19,7 +19,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, bool value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -33,12 +33,12 @@ namespace Discord.Serialization.Json.Converters
reader.Read();
if (reader.ValueType != JsonValueType.String)
throw new SerializationException("Bad input, expected String");
return Guid.Parse(reader.ParseString());
return Guid.Parse(reader.ParseString()); //TODO: Causes allocs
}
public void Write(PropertyMap map, ref JsonWriter writer, Guid value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value.ToString());
writer.WriteAttribute(map.Utf16Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


+ 4
- 4
src/Discord.Net.Serialization/Json/Converters/Primitives.Signed.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, sbyte value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, short value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, int value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, long value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value.ToString());
writer.WriteAttribute(map.Utf16Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


+ 1
- 1
src/Discord.Net.Serialization/Json/Converters/Primitives.String.cs View File

@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, string value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}


+ 4
- 4
src/Discord.Net.Serialization/Json/Converters/Primitives.Unsigned.cs View File

@@ -15,7 +15,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, byte value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -34,7 +34,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, ushort value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -53,7 +53,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, uint value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value);
writer.WriteAttribute(map.Utf16Key, value);
else
writer.WriteValue(value);
}
@@ -72,7 +72,7 @@ namespace Discord.Serialization.Json.Converters
public void Write(PropertyMap map, ref JsonWriter writer, ulong value, bool isTopLevel)
{
if (isTopLevel)
writer.WriteAttribute(map.Key, value.ToString());
writer.WriteAttribute(map.Utf16Key, value.ToString());
else
writer.WriteValue(value.ToString());
}


+ 3
- 1
src/Discord.Net.Serialization/Json/IJsonPropertyMap.cs View File

@@ -1,10 +1,12 @@
using System.Text.Json;
using System.Text.Utf8;

namespace Discord.Serialization
{
internal interface IJsonPropertyMap<TModel>
{
string Key { get; }
string Utf16Key { get; }
Utf8String Utf8Key { get; }

void Write(TModel model, ref JsonWriter writer);
void Read(TModel model, ref JsonReader reader);


+ 4
- 3
src/Discord.Net.Serialization/ModelMap.cs View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Discord.Serialization
@@ -7,9 +8,9 @@ namespace Discord.Serialization
where TModel : class, new()
{
public readonly PropertyMap[] Properties;
public readonly Dictionary<string, PropertyMap> PropertiesByKey;
public readonly Dictionary<ReadOnlySpan<byte>, PropertyMap> PropertiesByKey;

public ModelMap(Dictionary<string, PropertyMap> properties)
public ModelMap(Dictionary<ReadOnlySpan<byte>, PropertyMap> properties)
{
PropertiesByKey = properties;
Properties = PropertiesByKey.Values.ToArray();


+ 5
- 2
src/Discord.Net.Serialization/PropertyMap.cs View File

@@ -1,17 +1,20 @@
using System.Reflection;
using System.Text.Utf8;

namespace Discord.Serialization
{
public abstract class PropertyMap
{
public string Key { get; }
public string Utf16Key { get; }
public Utf8String Utf8Key { get; }
public bool ExcludeNull { get; }

public PropertyMap(PropertyInfo propInfo)
{
var jsonProperty = propInfo.GetCustomAttribute<ModelPropertyAttribute>();

Key = jsonProperty?.Key ?? propInfo.Name;
Utf16Key = jsonProperty?.Key ?? propInfo.Name;
Utf8Key = new Utf8String(Utf16Key);
ExcludeNull = jsonProperty?.ExcludeNull ?? false;
}
}


+ 6
- 8
src/Discord.Net.Serialization/SerializationFormat.cs View File

@@ -25,17 +25,15 @@ namespace Discord.Serialization
return _maps.GetOrAdd(typeof(TModel), _ =>
{
var type = typeof(TModel).GetTypeInfo();
var properties = new Dictionary<string, PropertyMap>();
var propInfos = type.DeclaredProperties
.Where(x => x.CanRead && x.CanWrite)
.ToArray();

var propInfos = type.DeclaredProperties.ToArray();
var properties = new Dictionary<ReadOnlySpan<byte>, PropertyMap>(propInfos.Length, Utf8SpanComparer.Instance);
for (int i = 0; i < propInfos.Length; i++)
{
var propInfo = propInfos[i];
if (!propInfo.CanRead || !propInfo.CanWrite)
continue;

var propMap = MapProperty<TModel>(propInfo);
properties.Add(propMap.Key, propMap);
var propMap = MapProperty<TModel>(propInfos[i]);
properties.Add(propMap.Utf8Key, propMap);
}
return new ModelMap<TModel>(properties);
}) as ModelMap<TModel>;


+ 42
- 0
src/Discord.Net.Serialization/Utf8SpanComparer.cs View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;

namespace Discord.Serialization
{
internal class Utf8SpanComparer : IEqualityComparer<ReadOnlySpan<byte>>
{
public static readonly Utf8SpanComparer Instance = new Utf8SpanComparer();

public bool Equals(ReadOnlySpan<byte> x, ReadOnlySpan<byte> y) => x.SequenceEqual(y);
public int GetHashCode(ReadOnlySpan<byte> obj)
{
//From Utf8String
//TODO: Replace when they do
unchecked
{
if (obj.Length <= 4)
{
int hash = obj.Length;
for (int i = 0; i < obj.Length; i++)
{
hash <<= 8;
hash ^= obj[i];
}
return hash;
}
else
{
int hash = obj.Length;
hash ^= obj[0];
hash <<= 8;
hash ^= obj[1];
hash <<= 8;
hash ^= obj[obj.Length - 2];
hash <<= 8;
hash ^= obj[obj.Length - 1];
return hash;
}
}
}
}
}

Loading…
Cancel
Save