Skip to content

Commit 4a92d80

Browse files
filmorlostmsu
andauthored
Improve performance of unwrapping .NET objects passed from Python (pythonnet#930)
This addresses the following scenario: 1. A C# object `a` is created and filled with some data. 2. `a` is passed via Python.NET to Python. To do that Python.NET creates a wrapper object `w`, and stores reference to `a` in one of its fields. 3. Python code later passes `w` back to C#, e.g. calls `SomeCSharpMethod(w)`. 4. Python.NET has to unwrap `w`, so it reads the reference to `a` from it. Prior to this change in 4. Python.NET had to determine what kind of an object `a` is. If it is an exception, a different offset needed to be used. That check was very expensive (up to 4 calls into Python interpreter). This change replaces that check with computing offset unconditionally by subtracting a constant from the object size (which is read from the wrapper), thus avoiding calls to Python interpreter. Co-authored-by: Victor Milovanov <lost@losttech.software>
1 parent 782eff8 commit 4a92d80

File tree

10 files changed

+113
-51
lines changed

10 files changed

+113
-51
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
2424
- Added argument types information to "No method matches given arguments" message
2525
- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures
2626
- Removes PyLong_GetMax and PyClass_New when targetting Python3
27+
- Improved performance of calls from Python to C#
2728
- Added support for converting python iterators to C# arrays
2829
- Changed usage of obselete function GetDelegateForFunctionPointer(IntPtr, Type) to GetDelegateForFunctionPointer<TDelegate>(IntPtr)
2930
- When calling C# from Python, enable passing argument of any type to a parameter of C# type `object` by wrapping it into `PyObject` instance. ([#881][i881])

src/perf_tests/BenchmarkTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ public void SetUp()
3030
public void ReadInt64Property()
3131
{
3232
double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports);
33-
AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.66);
33+
AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.57);
3434
}
3535

3636
[Test]
3737
public void WriteInt64Property()
3838
{
3939
double optimisticPerfRatio = GetOptimisticPerfRatio(this.summary.Reports);
40-
AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.64);
40+
AssertPerformanceIsBetterOrSame(optimisticPerfRatio, target: 0.57);
4141
}
4242

4343
static double GetOptimisticPerfRatio(

src/runtime/classbase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ public static IntPtr tp_repr(IntPtr ob)
291291
public static void tp_dealloc(IntPtr ob)
292292
{
293293
ManagedType self = GetManagedObject(ob);
294-
IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.DictOffset(ob));
294+
IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.TypeDictOffset(self.tpHandle));
295295
if (dict != IntPtr.Zero)
296296
{
297297
Runtime.XDecref(dict);

src/runtime/classderived.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,7 @@ public static void Finalize(IPythonDerivedType obj)
877877
// the C# object is being destroyed which must mean there are no more
878878
// references to the Python object as well so now we can dealloc the
879879
// python object.
880-
IntPtr dict = Marshal.ReadIntPtr(self.pyHandle, ObjectOffset.DictOffset(self.pyHandle));
880+
IntPtr dict = Marshal.ReadIntPtr(self.pyHandle, ObjectOffset.TypeDictOffset(self.tpHandle));
881881
if (dict != IntPtr.Zero)
882882
{
883883
Runtime.XDecref(dict);

src/runtime/clrobject.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ internal CLRObject(object ob, IntPtr tp)
1414
long flags = Util.ReadCLong(tp, TypeOffset.tp_flags);
1515
if ((flags & TypeFlags.Subclass) != 0)
1616
{
17-
IntPtr dict = Marshal.ReadIntPtr(py, ObjectOffset.DictOffset(tp));
17+
IntPtr dict = Marshal.ReadIntPtr(py, ObjectOffset.TypeDictOffset(tp));
1818
if (dict == IntPtr.Zero)
1919
{
2020
dict = Runtime.PyDict_New();
21-
Marshal.WriteIntPtr(py, ObjectOffset.DictOffset(tp), dict);
21+
Marshal.WriteIntPtr(py, ObjectOffset.TypeDictOffset(tp), dict);
2222
}
2323
}
2424

src/runtime/interop.cs

Lines changed: 96 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
3-
using System.Collections.Specialized;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
45
using System.Runtime.InteropServices;
56
using System.Reflection;
67
using System.Text;
@@ -68,11 +69,47 @@ public ModulePropertyAttribute()
6869
}
6970
}
7071

72+
internal static class ManagedDataOffsets
73+
{
74+
static ManagedDataOffsets()
75+
{
76+
FieldInfo[] fi = typeof(ManagedDataOffsets).GetFields(BindingFlags.Static | BindingFlags.Public);
77+
for (int i = 0; i < fi.Length; i++)
78+
{
79+
fi[i].SetValue(null, -(i * IntPtr.Size) - IntPtr.Size);
80+
}
7181

72-
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
73-
internal class ObjectOffset
82+
size = fi.Length * IntPtr.Size;
83+
}
84+
85+
public static readonly int ob_data;
86+
public static readonly int ob_dict;
87+
88+
private static int BaseOffset(IntPtr type)
89+
{
90+
Debug.Assert(type != IntPtr.Zero);
91+
int typeSize = Marshal.ReadInt32(type, TypeOffset.tp_basicsize);
92+
Debug.Assert(typeSize > 0 && typeSize <= ExceptionOffset.Size());
93+
return typeSize;
94+
}
95+
public static int DataOffset(IntPtr type)
96+
{
97+
return BaseOffset(type) + ob_data;
98+
}
99+
100+
public static int DictOffset(IntPtr type)
101+
{
102+
return BaseOffset(type) + ob_dict;
103+
}
104+
105+
public static int Size { get { return size; } }
106+
107+
private static readonly int size;
108+
}
109+
110+
internal static class OriginalObjectOffsets
74111
{
75-
static ObjectOffset()
112+
static OriginalObjectOffsets()
76113
{
77114
int size = IntPtr.Size;
78115
var n = 0; // Py_TRACE_REFS add two pointers to PyObject_HEAD
@@ -83,42 +120,58 @@ static ObjectOffset()
83120
#endif
84121
ob_refcnt = (n + 0) * size;
85122
ob_type = (n + 1) * size;
86-
ob_dict = (n + 2) * size;
87-
ob_data = (n + 3) * size;
88123
}
89124

90-
public static int magic(IntPtr ob)
125+
public static int Size { get { return size; } }
126+
127+
private static readonly int size =
128+
#if PYTHON_WITH_PYDEBUG
129+
4 * IntPtr.Size;
130+
#else
131+
2 * IntPtr.Size;
132+
#endif
133+
134+
#if PYTHON_WITH_PYDEBUG
135+
public static int _ob_next;
136+
public static int _ob_prev;
137+
#endif
138+
public static int ob_refcnt;
139+
public static int ob_type;
140+
}
141+
142+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
143+
internal class ObjectOffset
144+
{
145+
static ObjectOffset()
146+
{
147+
#if PYTHON_WITH_PYDEBUG
148+
_ob_next = OriginalObjectOffsets._ob_next;
149+
_ob_prev = OriginalObjectOffsets._ob_prev;
150+
#endif
151+
ob_refcnt = OriginalObjectOffsets.ob_refcnt;
152+
ob_type = OriginalObjectOffsets.ob_type;
153+
154+
size = OriginalObjectOffsets.Size + ManagedDataOffsets.Size;
155+
}
156+
157+
public static int magic(IntPtr type)
91158
{
92-
if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) ||
93-
(Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException))))
94-
{
95-
return ExceptionOffset.ob_data;
96-
}
97-
return ob_data;
159+
return ManagedDataOffsets.DataOffset(type);
98160
}
99161

100-
public static int DictOffset(IntPtr ob)
162+
public static int TypeDictOffset(IntPtr type)
101163
{
102-
if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) ||
103-
(Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException))))
104-
{
105-
return ExceptionOffset.ob_dict;
106-
}
107-
return ob_dict;
164+
return ManagedDataOffsets.DictOffset(type);
108165
}
109166

110-
public static int Size(IntPtr ob)
167+
public static int Size(IntPtr pyType)
111168
{
112-
if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) ||
113-
(Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException))))
169+
if (IsException(pyType))
114170
{
115171
return ExceptionOffset.Size();
116172
}
117-
#if PYTHON_WITH_PYDEBUG
118-
return 6 * IntPtr.Size;
119-
#else
120-
return 4 * IntPtr.Size;
121-
#endif
173+
174+
return size;
122175
}
123176

124177
#if PYTHON_WITH_PYDEBUG
@@ -127,8 +180,15 @@ public static int Size(IntPtr ob)
127180
#endif
128181
public static int ob_refcnt;
129182
public static int ob_type;
130-
private static int ob_dict;
131-
private static int ob_data;
183+
private static readonly int size;
184+
185+
private static bool IsException(IntPtr pyObject)
186+
{
187+
var type = Runtime.PyObject_TYPE(pyObject);
188+
return Runtime.PyType_IsSameAsOrSubtype(type, ofType: Exceptions.BaseException)
189+
|| Runtime.PyType_IsSameAsOrSubtype(type, ofType: Runtime.PyTypeType)
190+
&& Runtime.PyType_IsSubtype(pyObject, Exceptions.BaseException);
191+
}
132192
}
133193

134194
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
@@ -137,19 +197,17 @@ internal class ExceptionOffset
137197
static ExceptionOffset()
138198
{
139199
Type type = typeof(ExceptionOffset);
140-
FieldInfo[] fi = type.GetFields();
141-
int size = IntPtr.Size;
200+
FieldInfo[] fi = type.GetFields(BindingFlags.Static | BindingFlags.Public);
142201
for (int i = 0; i < fi.Length; i++)
143202
{
144-
fi[i].SetValue(null, (i * size) + ObjectOffset.ob_type + size);
203+
fi[i].SetValue(null, (i * IntPtr.Size) + OriginalObjectOffsets.Size);
145204
}
146-
}
147205

148-
public static int Size()
149-
{
150-
return ob_data + IntPtr.Size;
206+
size = fi.Length * IntPtr.Size + OriginalObjectOffsets.Size + ManagedDataOffsets.Size;
151207
}
152208

209+
public static int Size() { return size; }
210+
153211
// PyException_HEAD
154212
// (start after PyObject_HEAD)
155213
public static int dict = 0;
@@ -163,9 +221,7 @@ public static int Size()
163221
public static int suppress_context = 0;
164222
#endif
165223

166-
// extra c# data
167-
public static int ob_dict;
168-
public static int ob_data;
224+
private static readonly int size;
169225
}
170226

171227

src/runtime/managedtype.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ internal static ManagedType GetManagedObject(IntPtr ob)
3333
{
3434
IntPtr op = tp == ob
3535
? Marshal.ReadIntPtr(tp, TypeOffset.magic())
36-
: Marshal.ReadIntPtr(ob, ObjectOffset.magic(ob));
36+
: Marshal.ReadIntPtr(ob, ObjectOffset.magic(tp));
3737
if (op == IntPtr.Zero)
3838
{
3939
return null;

src/runtime/moduleobject.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public ModuleObject(string name)
5454
Runtime.XDecref(pyfilename);
5555
Runtime.XDecref(pydocstring);
5656

57-
Marshal.WriteIntPtr(pyHandle, ObjectOffset.DictOffset(pyHandle), dict);
57+
Marshal.WriteIntPtr(pyHandle, ObjectOffset.TypeDictOffset(tpHandle), dict);
5858

5959
InitializeModuleMembers();
6060
}

src/runtime/runtime.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1889,6 +1889,11 @@ internal static bool PyObject_TypeCheck(IntPtr ob, IntPtr tp)
18891889
return (t == tp) || PyType_IsSubtype(t, tp);
18901890
}
18911891

1892+
internal static bool PyType_IsSameAsOrSubtype(IntPtr type, IntPtr ofType)
1893+
{
1894+
return (type == ofType) || PyType_IsSubtype(type, ofType);
1895+
}
1896+
18921897
[DllImport(_PythonDll, CallingConvention = CallingConvention.Cdecl)]
18931898
internal static extern IntPtr PyType_GenericNew(IntPtr type, IntPtr args, IntPtr kw);
18941899

src/runtime/typemanager.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ internal static IntPtr CreateType(Type impl)
8787
// Set tp_basicsize to the size of our managed instance objects.
8888
Marshal.WriteIntPtr(type, TypeOffset.tp_basicsize, (IntPtr)ob_size);
8989

90-
var offset = (IntPtr)ObjectOffset.DictOffset(type);
90+
var offset = (IntPtr)ObjectOffset.TypeDictOffset(type);
9191
Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, offset);
9292

9393
InitializeSlots(type, impl);
@@ -125,17 +125,17 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
125125

126126
IntPtr base_ = IntPtr.Zero;
127127
int ob_size = ObjectOffset.Size(Runtime.PyTypeType);
128-
int tp_dictoffset = ObjectOffset.DictOffset(Runtime.PyTypeType);
129128

130129
// XXX Hack, use a different base class for System.Exception
131130
// Python 2.5+ allows new style class exceptions but they *must*
132131
// subclass BaseException (or better Exception).
133132
if (typeof(Exception).IsAssignableFrom(clrType))
134133
{
135134
ob_size = ObjectOffset.Size(Exceptions.Exception);
136-
tp_dictoffset = ObjectOffset.DictOffset(Exceptions.Exception);
137135
}
138136

137+
int tp_dictoffset = ob_size + ManagedDataOffsets.ob_dict;
138+
139139
if (clrType == typeof(Exception))
140140
{
141141
base_ = Exceptions.Exception;

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy