Skip to content

Commit

Permalink
Merge pull request #149 from tonybaloney/type-converter-perf
Browse files Browse the repository at this point in the history
type converter perf
tonybaloney authored Aug 21, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents 90dfaa1 + ed2992c commit afd35e1
Showing 5 changed files with 70 additions and 35 deletions.
32 changes: 21 additions & 11 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs
Original file line number Diff line number Diff line change
@@ -4,17 +4,28 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Reflection;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private object? ConvertToDictionary(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture, bool useMappingProtocol = false)
{
using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject));
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type);
IDictionary dict = (IDictionary)Activator.CreateInstance(dictType)!;
using PyObject items = useMappingProtocol ? new(CPythonAPI.PyMapping_Items(pyObject)) : new(CPythonAPI.PyDict_Items(pyObject));

Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type dictType = typeof(Dictionary<,>).MakeGenericType(item1Type, item2Type);
Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type);

typeInfo = new(returnType.GetConstructor([dictType])!, dictType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IDictionary dict = (IDictionary)typeInfo.TransientTypeConstructor!.Invoke([]);
nint itemsLength = CPythonAPI.PyList_Size(items);

for (nint i = 0; i < itemsLength; i++)
@@ -24,14 +35,13 @@ internal partial class PyObjectTypeConverter
using PyObject item1 = new(CPythonAPI.PyTuple_GetItem(item, 0));
using PyObject item2 = new(CPythonAPI.PyTuple_GetItem(item, 1));

object? convertedItem1 = AsManagedObject(item1Type, item1, context, culture);
object? convertedItem2 = AsManagedObject(item2Type, item2, context, culture);
object? convertedItem1 = ConvertTo(context, culture, item1, item1Type);
object? convertedItem2 = ConvertTo(context, culture, item2, item2Type);

dict.Add(convertedItem1!, convertedItem2);
}

Type returnType = typeof(ReadOnlyDictionary<,>).MakeGenericType(item1Type, item2Type);
return Activator.CreateInstance(returnType, dict);
}

return typeInfo.ReturnTypeConstructor.Invoke([dict]);
}

private PyObject ConvertFromDictionary(ITypeDescriptorContext? context, CultureInfo? culture, IDictionary dictionary)
18 changes: 12 additions & 6 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs
Original file line number Diff line number Diff line change
@@ -8,11 +8,17 @@ internal partial class PyObjectTypeConverter
{
private object? ConvertToGeneratorIterator(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type item3Type = destinationType.GetGenericArguments()[2];
Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type);
ConstructorInfo ctor = generatorType.GetConstructors().First();
return ctor.Invoke([pyObject.Clone()]);
if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type item1Type = destinationType.GetGenericArguments()[0];
Type item2Type = destinationType.GetGenericArguments()[1];
Type item3Type = destinationType.GetGenericArguments()[2];
Type generatorType = typeof(GeneratorIterator<,,>).MakeGenericType(item1Type, item2Type, item3Type);
ConstructorInfo ctor = generatorType.GetConstructors().First();
typeInfo = new(ctor);
knownDynamicTypes[destinationType] = typeInfo;
}

return typeInfo.ReturnTypeConstructor.Invoke([pyObject.Clone()]);
}
}
29 changes: 21 additions & 8 deletions src/CSnakes.Runtime/PyObjectTypeConverter.List.cs
Original file line number Diff line number Diff line change
@@ -9,29 +9,42 @@ internal partial class PyObjectTypeConverter
{
private object? ConvertToList(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type genericArgument = destinationType.GetGenericArguments()[0];
Type listType = typeof(List<>).MakeGenericType(genericArgument);
Type genericArgument = destinationType.GetGenericArguments()[0];

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type listType = typeof(List<>).MakeGenericType(genericArgument);
typeInfo = new(listType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IList list = (IList)Activator.CreateInstance(listType)!;
IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]);
for (var i = 0; i < CPythonAPI.PyList_Size(pyObject); i++)
{
using PyObject item = new(CPythonAPI.PyList_GetItem(pyObject, i));
list.Add(AsManagedObject(genericArgument, item, context, culture));
list.Add(ConvertTo(context, culture, item, genericArgument));
}

return list;
}

private object? ConvertToListFromSequence(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
Type genericArgument = destinationType.GetGenericArguments()[0];
Type listType = typeof(List<>).MakeGenericType(genericArgument);
Type genericArgument = destinationType.GetGenericArguments()[0];

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
Type listType = typeof(List<>).MakeGenericType(genericArgument);
typeInfo = new(listType.GetConstructor([])!);
knownDynamicTypes[destinationType] = typeInfo;
}

IList list = (IList)typeInfo.ReturnTypeConstructor.Invoke([]);

IList list = (IList)Activator.CreateInstance(listType)!;
for (var i = 0; i < CPythonAPI.PySequence_Size(pyObject); i++)
{
using PyObject item = new(CPythonAPI.PySequence_GetItem(pyObject, i));
list.Add(AsManagedObject(genericArgument, item, context, culture));
list.Add(ConvertTo(context, culture, item, genericArgument));
}

return list;
16 changes: 11 additions & 5 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo?
if (tupleValues.Count > 8)
{
// We are hitting nested tuples here, which will be treated in a different way.
object?[] firstSeven = tupleValues.Take(7).Select((p, i) => AsManagedObject(types[i], p, context, culture)).ToArray();
object?[] firstSeven = tupleValues.Take(7).Select((p, i) => ConvertTo(context, culture, p, types[i])).ToArray();

// Get the rest of the values and convert them to a nested tuple.
IEnumerable<PyObject> rest = tupleValues.Skip(7);
@@ -53,23 +53,29 @@ private PyObject ConvertFromTuple(ITypeDescriptorContext? context, CultureInfo?

// Use the decoder pipeline to decode the nested tuple (and its values).
// We do this because that means if we have nested nested tuples, they'll be decoded as well.
object? nestedTuple = AsManagedObject(types[7], pyTuple, context, culture);
object? nestedTuple = ConvertTo(context, culture, pyTuple, types[7]);

// Append our nested tuple to the first seven values.
clrValues = [.. firstSeven, nestedTuple];
}
else
{
clrValues = tupleValues.Select((p, i) => AsManagedObject(types[i], p, context, culture)).ToArray();
clrValues = tupleValues.Select((p, i) => ConvertTo(context, culture, p, types[i])).ToArray();
}

// Dispose of all the Python values that we captured from the Tuple.
foreach (var value in tupleValues)
{
value.Dispose();
}

if (!knownDynamicTypes.TryGetValue(destinationType, out DynamicTypeInfo? typeInfo))
{
ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length);
typeInfo = new(ctor);
knownDynamicTypes[destinationType] = typeInfo;
}

ConstructorInfo ctor = destinationType.GetConstructors().First(c => c.GetParameters().Length == clrValues.Length);
return (ITuple)ctor.Invoke(clrValues);
return (ITuple)typeInfo.ReturnTypeConstructor.Invoke(clrValues);
}
}
10 changes: 5 additions & 5 deletions src/CSnakes.Runtime/PyObjectTypeConverter.cs
Original file line number Diff line number Diff line change
@@ -6,10 +6,13 @@
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Numerics;
using System.Collections.Concurrent;
using System.Reflection;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter : TypeConverter
{
private readonly ConcurrentDictionary<Type, DynamicTypeInfo> knownDynamicTypes = [];

/// <summary>
/// Convert a Python object to a CLR managed object.
@@ -118,11 +121,6 @@ internal partial class PyObjectTypeConverter : TypeConverter
throw new InvalidCastException($"Attempting to cast {destinationType} from {pyObject.GetPythonType()}");
}

private object? AsManagedObject(Type type, PyObject p, ITypeDescriptorContext? context, CultureInfo? culture)
{
return ConvertTo(context, culture, p, type);
}

public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) =>
value switch
{
@@ -186,4 +184,6 @@ private PyObject ToPython(object? o, ITypeDescriptorContext? context, CultureInf

return result is null ? throw new NotImplementedException() : (PyObject)result;
}

record DynamicTypeInfo(ConstructorInfo ReturnTypeConstructor, ConstructorInfo? TransientTypeConstructor = null);
}

0 comments on commit afd35e1

Please sign in to comment.