Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 39add65

Browse files
lostmsufilmor
authored andcommitted
Improve performance of unwrapping .NET objects passed from Python
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.
1 parent f20bcf6 commit 39add65

File tree

8 files changed

+106
-49
lines changed

8 files changed

+106
-49
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1616
- Added argument types information to "No method matches given arguments" message
1717
- Moved wheel import in setup.py inside of a try/except to prevent pip collection failures
1818
- Removes PyLong_GetMax and PyClass_New when targetting Python3
19+
- Improved performance of calls from Python to C#
1920

2021
### Fixed
2122

src/runtime/classbase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ public static IntPtr tp_str(IntPtr ob)
253253
public static void tp_dealloc(IntPtr ob)
254254
{
255255
ManagedType self = GetManagedObject(ob);
256-
IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.DictOffset(ob));
256+
IntPtr dict = Marshal.ReadIntPtr(ob, ObjectOffset.TypeDictOffset(self.tpHandle));
257257
if (dict != IntPtr.Zero)
258258
{
259259
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;
@@ -67,11 +68,47 @@ public ModulePropertyAttribute()
6768
}
6869
}
6970

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

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

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

99-
public static int DictOffset(IntPtr ob)
156+
public static int magic(IntPtr type)
100157
{
101-
if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) ||
102-
(Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException))))
103-
{
104-
return ExceptionOffset.ob_dict;
105-
}
106-
return ob_dict;
158+
return ManagedDataOffsets.DataOffset(type);
107159
}
108160

109-
public static int Size(IntPtr ob)
161+
public static int TypeDictOffset(IntPtr type)
110162
{
111-
if ((Runtime.PyObject_TypeCheck(ob, Exceptions.BaseException) ||
112-
(Runtime.PyType_Check(ob) && Runtime.PyType_IsSubtype(ob, Exceptions.BaseException))))
163+
return ManagedDataOffsets.DictOffset(type);
164+
}
165+
166+
public static int Size(IntPtr pyType)
167+
{
168+
if (IsException(pyType))
113169
{
114170
return ExceptionOffset.Size();
115171
}
116-
#if PYTHON_WITH_PYDEBUG
117-
return 6 * IntPtr.Size;
118-
#else
119-
return 4 * IntPtr.Size;
120-
#endif
172+
173+
return size;
121174
}
122175

123176
#if PYTHON_WITH_PYDEBUG
@@ -126,8 +179,15 @@ public static int Size(IntPtr ob)
126179
#endif
127180
public static int ob_refcnt;
128181
public static int ob_type;
129-
private static int ob_dict;
130-
private static int ob_data;
182+
private static readonly int size;
183+
184+
private static bool IsException(IntPtr pyObject)
185+
{
186+
var type = Runtime.PyObject_TYPE(pyObject);
187+
return Runtime.PyObjectType_TypeCheck(type, Exceptions.BaseException)
188+
|| Runtime.PyObjectType_TypeCheck(type, Runtime.PyTypeType)
189+
&& Runtime.PyType_IsSubtype(pyObject, Exceptions.BaseException);
190+
}
131191
}
132192

133193
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
@@ -136,19 +196,17 @@ internal class ExceptionOffset
136196
static ExceptionOffset()
137197
{
138198
Type type = typeof(ExceptionOffset);
139-
FieldInfo[] fi = type.GetFields();
140-
int size = IntPtr.Size;
199+
FieldInfo[] fi = type.GetFields(BindingFlags.Static | BindingFlags.Public);
141200
for (int i = 0; i < fi.Length; i++)
142201
{
143-
fi[i].SetValue(null, (i * size) + ObjectOffset.ob_type + size);
202+
fi[i].SetValue(null, (i * IntPtr.Size) + OriginalObjectOffsets.Size);
144203
}
145-
}
146204

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

208+
public static int Size() { return size; }
209+
152210
// PyException_HEAD
153211
// (start after PyObject_HEAD)
154212
public static int dict = 0;
@@ -162,9 +220,7 @@ public static int Size()
162220
public static int suppress_context = 0;
163221
#endif
164222

165-
// extra c# data
166-
public static int ob_dict;
167-
public static int ob_data;
223+
private static readonly int size;
168224
}
169225

170226

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
@@ -53,7 +53,7 @@ public ModuleObject(string name)
5353
Runtime.XDecref(pyfilename);
5454
Runtime.XDecref(pydocstring);
5555

56-
Marshal.WriteIntPtr(pyHandle, ObjectOffset.DictOffset(pyHandle), dict);
56+
Marshal.WriteIntPtr(pyHandle, ObjectOffset.TypeDictOffset(tpHandle), dict);
5757

5858
InitializeModuleMembers();
5959
}

src/runtime/typemanager.cs

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

88-
var offset = (IntPtr)ObjectOffset.DictOffset(type);
88+
var offset = (IntPtr)ObjectOffset.TypeDictOffset(type);
8989
Marshal.WriteIntPtr(type, TypeOffset.tp_dictoffset, offset);
9090

9191
InitializeSlots(type, impl);
@@ -123,17 +123,17 @@ internal static IntPtr CreateType(ManagedType impl, Type clrType)
123123

124124
IntPtr base_ = IntPtr.Zero;
125125
int ob_size = ObjectOffset.Size(Runtime.PyTypeType);
126-
int tp_dictoffset = ObjectOffset.DictOffset(Runtime.PyTypeType);
127126

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

135+
int tp_dictoffset = ob_size + ManagedDataOffsets.ob_dict;
136+
137137
if (clrType == typeof(Exception))
138138
{
139139
base_ = Exceptions.Exception;

0 commit comments

Comments
 (0)