Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

type converter perf #149

Merged
merged 2 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Dictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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++)
Expand All @@ -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)
Expand Down
18 changes: 12 additions & 6 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Up @@ -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;
Expand Down
16 changes: 11 additions & 5 deletions src/CSnakes.Runtime/PyObjectTypeConverter.Tuple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Up @@ -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.
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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);
}
Loading