Skip to content

Commit 1aa5b8b

Browse files
authored
Refactor enums comparison operators for performance improvements (#105)
* Refactor enums comparison operators for performance improvements * Minor change * Minor change * Minor improvements * Minor change * Cleanup * Update version to 2.0.47
1 parent 1d09c98 commit 1aa5b8b

File tree

8 files changed

+277
-466
lines changed

8 files changed

+277
-466
lines changed

src/perf_tests/Python.PerformanceTests.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1414
</PackageReference>
1515
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.*" />
16-
<PackageReference Include="quantconnect.pythonnet" Version="2.0.46" GeneratePathProperty="true">
16+
<PackageReference Include="quantconnect.pythonnet" Version="2.0.47" GeneratePathProperty="true">
1717
<IncludeAssets>compile</IncludeAssets>
1818
</PackageReference>
1919
</ItemGroup>
@@ -25,7 +25,7 @@
2525
</Target>
2626

2727
<Target Name="CopyBaseline" AfterTargets="Build">
28-
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.46\lib\net9.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
28+
<Copy SourceFiles="$(NuGetPackageRoot)quantconnect.pythonnet\2.0.47\lib\net9.0\Python.Runtime.dll" DestinationFolder="$(OutDir)baseline" />
2929
</Target>
3030

3131
<Target Name="CopyNewBuild" AfterTargets="Build">

src/runtime/ClassManager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,11 @@ internal static ClassBase CreateClass(Type type)
220220
impl = new LookUpObject(type);
221221
}
222222

223+
else if (type.IsEnum)
224+
{
225+
impl = new EnumObject(type);
226+
}
227+
223228
else
224229
{
225230
impl = new ClassObject(type);

src/runtime/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
[assembly: InternalsVisibleTo("Python.EmbeddingTest, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
55
[assembly: InternalsVisibleTo("Python.Test, PublicKey=00240000048000009400000006020000002400005253413100040000110000005ffd8f49fb44ab0641b3fd8d55e749f716e6dd901032295db641eb98ee46063cbe0d4a1d121ef0bc2af95f8a7438d7a80a3531316e6b75c2dae92fb05a99f03bf7e0c03980e1c3cfb74ba690aca2f3339ef329313bcc5dccced125a4ffdc4531dcef914602cd5878dc5fbb4d4c73ddfbc133f840231343e013762884d6143189")]
66

7-
[assembly: AssemblyVersion("2.0.46")]
8-
[assembly: AssemblyFileVersion("2.0.46")]
7+
[assembly: AssemblyVersion("2.0.47")]
8+
[assembly: AssemblyFileVersion("2.0.47")]

src/runtime/Python.Runtime.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<RootNamespace>Python.Runtime</RootNamespace>
66
<AssemblyName>Python.Runtime</AssemblyName>
77
<PackageId>QuantConnect.pythonnet</PackageId>
8-
<Version>2.0.46</Version>
8+
<Version>2.0.47</Version>
99
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
1010
<PackageLicenseFile>LICENSE</PackageLicenseFile>
1111
<RepositoryUrl>https://github.com/pythonnet/pythonnet</RepositoryUrl>

src/runtime/Types/ClassBase.cs

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -156,42 +156,7 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc
156156
try
157157
{
158158
int cmp = co1Comp.CompareTo(co2Inst);
159-
160-
BorrowedReference pyCmp;
161-
if (cmp < 0)
162-
{
163-
if (op == Runtime.Py_LT || op == Runtime.Py_LE)
164-
{
165-
pyCmp = Runtime.PyTrue;
166-
}
167-
else
168-
{
169-
pyCmp = Runtime.PyFalse;
170-
}
171-
}
172-
else if (cmp == 0)
173-
{
174-
if (op == Runtime.Py_LE || op == Runtime.Py_GE)
175-
{
176-
pyCmp = Runtime.PyTrue;
177-
}
178-
else
179-
{
180-
pyCmp = Runtime.PyFalse;
181-
}
182-
}
183-
else
184-
{
185-
if (op == Runtime.Py_GE || op == Runtime.Py_GT)
186-
{
187-
pyCmp = Runtime.PyTrue;
188-
}
189-
else
190-
{
191-
pyCmp = Runtime.PyFalse;
192-
}
193-
}
194-
return new NewReference(pyCmp);
159+
return new NewReference(GetComparisonResult(op, cmp));
195160
}
196161
catch (ArgumentException e)
197162
{
@@ -202,7 +167,53 @@ public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReferenc
202167
}
203168
}
204169

205-
private static bool TryGetSecondCompareOperandInstance(BorrowedReference left, BorrowedReference right, out CLRObject co1, out object co2Inst)
170+
/// <summary>
171+
/// Get the result of a comparison operation based on the operator and the comparison result.
172+
/// </summary>
173+
/// <remarks>
174+
/// This method is used to determine the result of a comparison operation, excluding equality and inequality.
175+
/// </remarks>
176+
protected static BorrowedReference GetComparisonResult(int op, int comparisonResult)
177+
{
178+
BorrowedReference pyCmp;
179+
if (comparisonResult < 0)
180+
{
181+
if (op == Runtime.Py_LT || op == Runtime.Py_LE)
182+
{
183+
pyCmp = Runtime.PyTrue;
184+
}
185+
else
186+
{
187+
pyCmp = Runtime.PyFalse;
188+
}
189+
}
190+
else if (comparisonResult == 0)
191+
{
192+
if (op == Runtime.Py_LE || op == Runtime.Py_GE)
193+
{
194+
pyCmp = Runtime.PyTrue;
195+
}
196+
else
197+
{
198+
pyCmp = Runtime.PyFalse;
199+
}
200+
}
201+
else
202+
{
203+
if (op == Runtime.Py_GE || op == Runtime.Py_GT)
204+
{
205+
pyCmp = Runtime.PyTrue;
206+
}
207+
else
208+
{
209+
pyCmp = Runtime.PyFalse;
210+
}
211+
}
212+
213+
return pyCmp;
214+
}
215+
216+
protected static bool TryGetSecondCompareOperandInstance(BorrowedReference left, BorrowedReference right, out CLRObject co1, out object co2Inst)
206217
{
207218
co2Inst = null;
208219

src/runtime/Types/DelegateObject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static NewReference tp_call(BorrowedReference ob, BorrowedReference args,
103103
/// <summary>
104104
/// Implements __cmp__ for reflected delegate types.
105105
/// </summary>
106-
public new static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op)
106+
public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op)
107107
{
108108
if (op != Runtime.Py_EQ && op != Runtime.Py_NE)
109109
{

src/runtime/Types/EnumObject.cs

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Python.Runtime
5+
{
6+
/// <summary>
7+
/// Managed class that provides the implementation for reflected enum types.
8+
/// </summary>
9+
[Serializable]
10+
internal class EnumObject : ClassBase
11+
{
12+
internal EnumObject(Type type) : base(type)
13+
{
14+
}
15+
16+
/// <summary>
17+
/// Standard comparison implementation for instances of enum types.
18+
/// </summary>
19+
public static NewReference tp_richcompare(BorrowedReference ob, BorrowedReference other, int op)
20+
{
21+
object rightInstance;
22+
CLRObject leftClrObject;
23+
int comparisonResult;
24+
25+
switch (op)
26+
{
27+
case Runtime.Py_EQ:
28+
case Runtime.Py_NE:
29+
var pytrue = Runtime.PyTrue;
30+
var pyfalse = Runtime.PyFalse;
31+
32+
// swap true and false for NE
33+
if (op != Runtime.Py_EQ)
34+
{
35+
pytrue = Runtime.PyFalse;
36+
pyfalse = Runtime.PyTrue;
37+
}
38+
39+
if (ob == other)
40+
{
41+
return new NewReference(pytrue);
42+
}
43+
44+
if (!TryGetSecondCompareOperandInstance(ob, other, out leftClrObject, out rightInstance))
45+
{
46+
return new NewReference(pyfalse);
47+
}
48+
49+
if (rightInstance != null &&
50+
TryCompare(leftClrObject.inst as Enum, rightInstance, out comparisonResult) &&
51+
comparisonResult == 0)
52+
{
53+
return new NewReference(pytrue);
54+
}
55+
else
56+
{
57+
return new NewReference(pyfalse);
58+
}
59+
60+
case Runtime.Py_LT:
61+
case Runtime.Py_LE:
62+
case Runtime.Py_GT:
63+
case Runtime.Py_GE:
64+
if (!TryGetSecondCompareOperandInstance(ob, other, out leftClrObject, out rightInstance))
65+
{
66+
return Exceptions.RaiseTypeError("Cannot get managed object");
67+
}
68+
69+
if (rightInstance == null)
70+
{
71+
return Exceptions.RaiseTypeError($"Cannot compare {leftClrObject.inst.GetType()} to None");
72+
}
73+
74+
try
75+
{
76+
if (!TryCompare(leftClrObject.inst as Enum, rightInstance, out comparisonResult))
77+
{
78+
return Exceptions.RaiseTypeError($"Cannot compare {leftClrObject.inst.GetType()} with {rightInstance.GetType()}");
79+
}
80+
81+
return new NewReference(GetComparisonResult(op, comparisonResult));
82+
}
83+
catch (ArgumentException e)
84+
{
85+
return Exceptions.RaiseTypeError(e.Message);
86+
}
87+
88+
default:
89+
return new NewReference(Runtime.PyNotImplemented);
90+
}
91+
}
92+
93+
/// <summary>
94+
/// Tries comparing the give enum to the right operand by converting it to the appropriate type if possible
95+
/// </summary>
96+
/// <returns>True if the right operand was converted to a supported type and the comparison was performed successfully</returns>
97+
private static bool TryCompare(Enum left, object right, out int result)
98+
{
99+
result = int.MinValue;
100+
var conversionSuccessful = true;
101+
var leftType = left.GetType();
102+
var rightType = right.GetType();
103+
104+
// Same enum comparison:
105+
if (leftType == rightType)
106+
{
107+
result = left.CompareTo(right);
108+
}
109+
// Comparison with other enum types
110+
else if (rightType.IsEnum)
111+
{
112+
var leftIsUnsigned = leftType.GetEnumUnderlyingType() == typeof(UInt64);
113+
result = Compare(left, right as Enum, leftIsUnsigned);
114+
}
115+
else if (right is string rightString)
116+
{
117+
result = left.ToString().CompareTo(rightString);
118+
}
119+
else
120+
{
121+
var leftIsUnsigned = leftType.GetEnumUnderlyingType() == typeof(UInt64);
122+
switch (right)
123+
{
124+
case double rightDouble:
125+
result = Compare(left, rightDouble, leftIsUnsigned);
126+
break;
127+
case long rightLong:
128+
result = Compare(left, rightLong, leftIsUnsigned);
129+
break;
130+
case ulong rightULong:
131+
result = Compare(left, rightULong, leftIsUnsigned);
132+
break;
133+
case int rightInt:
134+
result = Compare(left, (long)rightInt, leftIsUnsigned);
135+
break;
136+
case uint rightUInt:
137+
result = Compare(left, (ulong)rightUInt, leftIsUnsigned);
138+
break;
139+
default:
140+
conversionSuccessful = false;
141+
break;
142+
}
143+
}
144+
145+
return conversionSuccessful;
146+
}
147+
148+
#region Comparison against integers
149+
150+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
151+
private static int Compare(long a, ulong b)
152+
{
153+
if (a < 0) return -1;
154+
return ((ulong)a).CompareTo(b);
155+
}
156+
157+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
158+
private static int Compare(Enum a, long b, bool isUnsigned)
159+
{
160+
161+
if (isUnsigned)
162+
{
163+
return -Compare(b, Convert.ToUInt64(a));
164+
}
165+
return Convert.ToInt64(a).CompareTo(b);
166+
}
167+
168+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
169+
private static int Compare(Enum a, ulong b, bool inUnsigned)
170+
{
171+
if (inUnsigned)
172+
{
173+
return Convert.ToUInt64(a).CompareTo(b);
174+
}
175+
return Compare(Convert.ToInt64(a), b);
176+
}
177+
178+
#endregion
179+
180+
#region Comparison against doubles
181+
182+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
183+
private static int Compare(Enum a, double b, bool isUnsigned)
184+
{
185+
if (isUnsigned)
186+
{
187+
var uIntA = Convert.ToUInt64(a);
188+
if (uIntA < b) return -1;
189+
if (uIntA > b) return 1;
190+
return 0;
191+
}
192+
193+
var intA = Convert.ToInt64(a);
194+
if (intA < b) return -1;
195+
if (intA > b) return 1;
196+
return 0;
197+
}
198+
199+
#endregion
200+
201+
#region Comparison against other enum types
202+
203+
/// <summary>
204+
/// We support comparing enums of different types by comparing their underlying values.
205+
/// </summary>
206+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
207+
private static int Compare(Enum a, Enum b, bool isUnsigned)
208+
{
209+
if (b.GetType().GetEnumUnderlyingType() == typeof(UInt64))
210+
{
211+
return Compare(a, Convert.ToUInt64(b), isUnsigned);
212+
}
213+
return Compare(a, Convert.ToInt64(b), isUnsigned);
214+
}
215+
216+
#endregion
217+
}
218+
}

0 commit comments

Comments
 (0)