Skip to content

Commit

Permalink
Merge pull request #159 from tonybaloney/profile_app
Browse files Browse the repository at this point in the history
Improve performance by 2x and add a basic benchmark test
  • Loading branch information
tonybaloney authored Aug 28, 2024
2 parents d0c26c6 + 94227c9 commit 4090d5b
Show file tree
Hide file tree
Showing 41 changed files with 1,026 additions and 471 deletions.
21 changes: 2 additions & 19 deletions src/CSnakes.Runtime.Tests/Converter/BigIntegerConverterTest.cs
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
using CSnakes.Runtime.Python;
using System.ComponentModel;
using System.Numerics;

namespace CSnakes.Runtime.Tests.Converter;

public class BigIntegerConverterTest : RuntimeTestBase
public class BigIntegerConverterTest : ConverterTestBase
{
[Fact]
public void TestVeryBigNumbers()
{
const string number = "12345678987654345678764345678987654345678765";
TypeConverter td = TypeDescriptor.GetConverter(typeof(PyObject));
// Something that is too big for a long (I8)
BigInteger input = BigInteger.Parse(number);

Assert.True(td.CanConvertFrom(typeof(BigInteger)));

using (GIL.Acquire())
{
using PyObject? pyObj = td.ConvertFrom(input) as PyObject;

Assert.NotNull(pyObj);
Assert.Equal(number, pyObj!.ToString());

Assert.True(td.CanConvertTo(typeof(BigInteger)));

// Convert back
BigInteger integer = (BigInteger) td.ConvertTo(pyObj, typeof(BigInteger))!;
Assert.Equal(input, integer);
}
RunTest(input);
}
}
22 changes: 21 additions & 1 deletion src/CSnakes.Runtime.Tests/Python/PyObjectTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CSnakes.Runtime.Python;
using System.Collections;

namespace CSnakes.Runtime.Tests.Python;
public class PyObjectTests : RuntimeTestBase
Expand Down Expand Up @@ -61,7 +62,8 @@ public void TestObjectIsNone()
}

[Fact]
public void TestObjectIsSmallIntegers() {
public void TestObjectIsSmallIntegers()
{
// Small numbers are the same object in Python, weird implementation detail.
var obj1 = PyObject.From(42);
var obj2 = PyObject.From(42);
Expand Down Expand Up @@ -157,4 +159,22 @@ public void TestObjectNotStrictInequality(object? o1, object? o2, bool expectedL
Assert.Equal(expectedLT, obj1 <= obj2);
Assert.Equal(expectedGT, obj1 >= obj2);
}

[Fact]
public void TestAsCollection()
{
using PyObject o = PyObject.From<IEnumerable<string>>(new[] { "Hello", "World" })!;
var collection = o.AsCollection<IReadOnlyCollection<string>, string>();
Assert.NotNull(collection);
Assert.Equal(2, collection!.Count());
}

[Fact]
public void TestAsDictionary()
{
using PyObject o = PyObject.From<IDictionary<string, string>>(new Dictionary<string, string> { { "Hello", "World" } })!;
var dictionary = o.AsDictionary<IReadOnlyDictionary<string, string>, string, string>();
Assert.NotNull(dictionary);
Assert.Equal("World", dictionary!["Hello"]);
}
}
3 changes: 2 additions & 1 deletion src/CSnakes.Runtime.Tests/RuntimeTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public RuntimeTestBase()
var pb = services.WithPython();
pb.WithHome(Environment.CurrentDirectory);

pb.FromNuGet(pythonVersionWindows)
pb
.FromNuGet(pythonVersionWindows)
.FromMacOSInstallerLocator(pythonVersionMacOS)
.FromEnvironmentVariable("Python3_ROOT_DIR", pythonVersionLinux); // This last one is for GitHub Actions

Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Runtime/CPython/Bool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal unsafe partial class CPythonAPI

public static bool IsPyBool(PyObject p)
{
return PyObject_IsInstance(p, PyBoolType);
return p.DangerousGetHandle() == Py_True || p.DangerousGetHandle() == Py_False;
}

public static bool IsPyTrue(PyObject p)
Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes.Runtime/CPython/Dict.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal static nint PackDict(Span<string> kwnames, Span<IntPtr> kwvalues)
int result = PyDict_SetItemRaw(dict, keyObj, kwvalues[i]);
if (result == -1)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_DecRefRaw(keyObj);
}
Expand All @@ -55,7 +55,7 @@ internal static nint PyDict_GetItem(PyObject dict, PyObject key)
var result = PyDict_GetItem_(dict, key);
if (result == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(result);
return result;
Expand Down
14 changes: 12 additions & 2 deletions src/CSnakes.Runtime/CPython/Exceptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,18 @@ internal unsafe partial class CPythonAPI
/// Has an error occured. Caller must hold the GIL.
/// </summary>
/// <returns></returns>
[LibraryImport(PythonLibraryName)]
internal static partial nint PyErr_Occurred();
internal static bool PyErr_Occurred()
{
return PyErr_Occurred_() != IntPtr.Zero;
}

/// <summary>
/// Keep this function private. Use PyErr_Occurred() instead.
/// It returns a borrowed reference to the exception object. USe PyErr_Fetch if you need the exception.
/// </summary>
/// <returns></returns>
[LibraryImport(PythonLibraryName, EntryPoint = "PyErr_Occurred")]
private static partial nint PyErr_Occurred_();

[LibraryImport(PythonLibraryName)]
internal static partial void PyErr_Clear();
Expand Down
14 changes: 12 additions & 2 deletions src/CSnakes.Runtime/CPython/Float.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,23 @@ internal unsafe partial class CPythonAPI
[LibraryImport(PythonLibraryName)]
internal static partial nint PyFloat_FromDouble(double value);

internal static double PyFloat_AsDouble(PyObject p)
{
double result = PyFloat_AsDouble_(p);
if (result == -1 && PyErr_Occurred())
{
throw PyObject.ThrowPythonExceptionAsClrException("Error converting Python object to double, check that the object was a Python float. See InnerException for details.");
}
return result;
}

/// <summary>
/// Convery a PyFloat to a C double
/// </summary>
/// <param name="p"></param>
/// <returns>The double value</returns>
[LibraryImport(PythonLibraryName)]
internal static partial double PyFloat_AsDouble(PyObject obj);
[LibraryImport(PythonLibraryName, EntryPoint = "PyFloat_AsDouble")]
private static partial double PyFloat_AsDouble_(PyObject obj);

internal static bool IsPyFloat(PyObject p)
{
Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Runtime/CPython/Import.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ protected static nint GetBuiltin(string name)
nint attr = PyObject_GetAttrRaw(module, pyAttrName);
if (attr == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_DecRefRaw(pyName);
Py_DecRefRaw(pyAttrName);
Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Runtime/CPython/Init.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private void InitializeEmbeddedPython()
// Setup type statics
using (GIL.Acquire())
{
if (PyErr_Occurred() == 1)
if (PyErr_Occurred())
throw new InvalidOperationException("Python initialization failed.");

if (!IsInitialized)
Expand Down
2 changes: 1 addition & 1 deletion src/CSnakes.Runtime/CPython/List.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal static nint PyList_GetItem(PyObject obj, nint pos)
nint item = PyList_GetItem_(obj, pos);
if (item == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(item);
return item;
Expand Down
37 changes: 35 additions & 2 deletions src/CSnakes.Runtime/CPython/Long.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,41 @@ internal unsafe partial class CPythonAPI
[LibraryImport(PythonLibraryName)]
internal static partial nint PyLong_FromLongLong(long v);

[LibraryImport(PythonLibraryName)]
internal static partial long PyLong_AsLongLong(PyObject p);
/// <summary>
/// Calls PyLong_AsLongLong and throws a Python Exception if an error occurs.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
internal static long PyLong_AsLongLong(PyObject p)
{
long result = PyLong_AsLongLong_(p);
if (result == -1 && PyErr_Occurred())
{
throw PyObject.ThrowPythonExceptionAsClrException("Error converting Python object to int, check that the object was a Python int or that the value wasn't too large. See InnerException for details.");
}
return result;
}

[LibraryImport(PythonLibraryName, EntryPoint = "PyLong_AsLongLong")]
private static partial long PyLong_AsLongLong_(PyObject p);

/// <summary>
/// Calls PyLong_AsLong and throws a Python Exception if an error occurs.
/// </summary>
/// <param name="p"></param>
/// <returns></returns>
internal static long PyLong_AsLong(PyObject p)
{
long result = PyLong_AsLong_(p);
if (result == -1 && PyErr_Occurred())
{
throw PyObject.ThrowPythonExceptionAsClrException("Error converting Python object to int, check that the object was a Python int or that the value wasn't too large. See InnerException for details.");
}
return result;
}

[LibraryImport(PythonLibraryName, EntryPoint = "PyLong_AsLong")]
private static partial int PyLong_AsLong_(PyObject p);

internal static bool IsPyLong(PyObject p)
{
Expand Down
4 changes: 2 additions & 2 deletions src/CSnakes.Runtime/CPython/Object.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ internal static bool PyObject_IsInstance(PyObject ob, IntPtr type)
int result = PyObject_IsInstance_(ob, type);
if (result == -1)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
return result == 1;
}
Expand Down Expand Up @@ -173,7 +173,7 @@ internal static bool PyObject_RichCompare(PyObject ob1, PyObject ob2, RichCompar
int result = PyObject_RichCompareBool(ob1, ob2, comparisonType);
if (result == -1)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
return result == 1;
}
Expand Down
22 changes: 18 additions & 4 deletions src/CSnakes.Runtime/CPython/Tuple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,19 +67,33 @@ internal static int PyTuple_SetItemRaw(nint ob, nint pos, nint o)
/// <param name="pos">the index position as ssize_t</param>
/// <returns>A new reference to the item.</returns>
/// <exception cref="IndexOutOfRangeException"></exception>
internal static nint PyTuple_GetItem(PyObject ob, nint pos)
internal static nint PyTuple_GetItemWithNewRef(PyObject ob, nint pos)
{
nint item = PyTuple_GetItem_(ob, pos);
nint item = PyTuple_GetItem(ob, pos);
if (item == IntPtr.Zero)
{
PyObject.ThrowPythonExceptionAsClrException();
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(item);
return item;
}

internal static nint PyTuple_GetItemWithNewRefRaw(nint ob, nint pos)
{
nint item = PyTuple_GetItemRaw(ob, pos);
if (item == IntPtr.Zero)
{
throw PyObject.ThrowPythonExceptionAsClrException();
}
Py_IncRefRaw(item);
return item;
}

[LibraryImport(PythonLibraryName, EntryPoint = "PyTuple_GetItem")]
private static partial nint PyTuple_GetItem(PyObject ob, nint pos);

[LibraryImport(PythonLibraryName, EntryPoint = "PyTuple_GetItem")]
private static partial nint PyTuple_GetItem_(PyObject ob, nint pos);
private static partial nint PyTuple_GetItemRaw(nint ob, nint pos);

[LibraryImport(PythonLibraryName)]
internal static partial nint PyTuple_Size(PyObject p);
Expand Down
24 changes: 22 additions & 2 deletions src/CSnakes.Runtime/CPython/Unicode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,28 @@ internal static nint AsPyUnicodeObject(string s)
[LibraryImport(PythonLibraryName)]
internal static partial nint PyUnicode_DecodeUTF16(char* str, nint size, IntPtr errors, IntPtr byteorder);

[LibraryImport(PythonLibraryName, StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(NonFreeUtf8StringMarshaller))]
internal static partial string? PyUnicode_AsUTF8(PyObject s);
/// <summary>
/// Calls PyUnicode_AsUTF8 and throws a Python Exception if an error occurs.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
internal static string PyUnicode_AsUTF8(PyObject s)
{
var result = PyUnicode_AsUTF8_(s);
return result is null ? throw PyObject.ThrowPythonExceptionAsClrException() : result;
}

/// <summary>
/// Convert the string object to a UTF-8 encoded string and return a pointer to the internal buffer.
/// This function does a type check and returns null if the object is not a string.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
[LibraryImport(PythonLibraryName, StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(NonFreeUtf8StringMarshaller), EntryPoint = "PyUnicode_AsUTF8")]
private static partial string? PyUnicode_AsUTF8_(PyObject s);

[LibraryImport(PythonLibraryName, StringMarshalling = StringMarshalling.Custom, StringMarshallingCustomType = typeof(NonFreeUtf8StringMarshaller), EntryPoint = "PyUnicode_AsUTF8")]
internal static partial string? PyUnicode_AsUTF8Raw(nint s);

public static bool IsPyUnicode(PyObject p)
{
Expand Down
10 changes: 3 additions & 7 deletions src/CSnakes.Runtime/PyObjectTypeConverter.BigInteger.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
using CSnakes.Runtime.CPython;
using CSnakes.Runtime.Python;
using System.ComponentModel;
using System.Globalization;
using System.Numerics;

namespace CSnakes.Runtime;
internal partial class PyObjectTypeConverter
{
private object? ConvertToBigInteger(PyObject pyObject, Type destinationType, ITypeDescriptorContext? context, CultureInfo? culture)
{
internal static BigInteger ConvertToBigInteger(PyObject pyObject, Type destinationType) =>
// There is no practical API for this in CPython. Use str() instead.
return BigInteger.Parse(pyObject.ToString());
}
BigInteger.Parse(pyObject.ToString());

private PyObject ConvertFromBigInteger(ITypeDescriptorContext? context, CultureInfo? culture, BigInteger integer)
internal static PyObject ConvertFromBigInteger(BigInteger integer)
{
using PyObject pyUnicode = PyObject.Create(CPythonAPI.AsPyUnicodeObject(integer.ToString()));
return PyObject.Create(CPythonAPI.PyLong_FromUnicodeObject(pyUnicode, 10));
Expand Down
Loading

0 comments on commit 4090d5b

Please sign in to comment.