Skip to content

Commit fc549e3

Browse files
author
dse
committed
Managed to python string marshaling cache added.
1 parent 741070b commit fc549e3

File tree

2 files changed

+107
-21
lines changed

2 files changed

+107
-21
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
88
## [unreleased][]
99

1010
### Added
11+
- Improved performance. String marshaling between python and clr now cached.
12+
Cache reduces GC pressure and saves from extensive memory copying.
1113
- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0)
1214
- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild).
1315
Currently there two side-by-side build systems that produces the same output (net40) from the same sources.

src/runtime/CustomMarshaler.cs

+105-21
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public object MarshalNativeToManaged(IntPtr pNativeData)
1818

1919
public abstract IntPtr MarshalManagedToNative(object managedObj);
2020

21-
public void CleanUpNativeData(IntPtr pNativeData)
21+
public virtual void CleanUpNativeData(IntPtr pNativeData)
2222
{
2323
Marshal.FreeHGlobal(pNativeData);
2424
}
@@ -44,7 +44,12 @@ internal class UcsMarshaler : MarshalerBase
4444
private static readonly MarshalerBase Instance = new UcsMarshaler();
4545
private static readonly Encoding PyEncoding = Runtime.PyEncoding;
4646

47-
public override IntPtr MarshalManagedToNative(object managedObj)
47+
private const int MaxStringLength = 100;
48+
private const int MaxItemSize = 4 * (MaxStringLength + 1);
49+
private static readonly EncodedStringsFifoDictionary EncodedStringsDictionary =
50+
new EncodedStringsFifoDictionary(10000, MaxItemSize);
51+
52+
public override unsafe IntPtr MarshalManagedToNative(object managedObj)
4853
{
4954
var s = managedObj as string;
5055

@@ -53,16 +58,36 @@ public override IntPtr MarshalManagedToNative(object managedObj)
5358
return IntPtr.Zero;
5459
}
5560

56-
byte[] bStr = PyEncoding.GetBytes(s + "\0");
57-
IntPtr mem = Marshal.AllocHGlobal(bStr.Length);
58-
try
61+
IntPtr mem;
62+
int stringBytesCount;
63+
if (s.Length <= MaxStringLength)
5964
{
60-
Marshal.Copy(bStr, 0, mem, bStr.Length);
65+
if (EncodedStringsDictionary.TryGetValue(s, out mem))
66+
{
67+
return mem;
68+
}
69+
70+
stringBytesCount = PyEncoding.GetByteCount(s);
71+
mem = EncodedStringsDictionary.AddUnsafe(s);
6172
}
62-
catch (Exception)
73+
else
6374
{
64-
Marshal.FreeHGlobal(mem);
65-
throw;
75+
stringBytesCount = PyEncoding.GetByteCount(s);
76+
mem = Marshal.AllocHGlobal(stringBytesCount + 4);
77+
}
78+
79+
fixed (char* str = s)
80+
{
81+
try
82+
{
83+
PyEncoding.GetBytes(str, s.Length, (byte*)mem, stringBytesCount);
84+
}
85+
catch
86+
{
87+
// Do nothing with this. Very strange problem.
88+
}
89+
90+
*(int*)(mem + stringBytesCount) = 0;
6691
}
6792

6893
return mem;
@@ -106,6 +131,14 @@ public static int GetUnicodeByteLength(IntPtr p)
106131
}
107132
}
108133

134+
public override void CleanUpNativeData(IntPtr pNativeData)
135+
{
136+
if (!EncodedStringsDictionary.IsKnownPtr(pNativeData))
137+
{
138+
base.CleanUpNativeData(pNativeData);
139+
}
140+
}
141+
109142
/// <summary>
110143
/// Utility function for Marshaling Unicode on PY3 and AnsiStr on PY2.
111144
/// Use on functions whose Input signatures changed between PY2/PY3.
@@ -118,11 +151,29 @@ public static int GetUnicodeByteLength(IntPtr p)
118151
/// <remarks>
119152
/// You MUST deallocate the IntPtr of the Return when done with it.
120153
/// </remarks>
121-
public static IntPtr Py3UnicodePy2StringtoPtr(string s)
154+
public unsafe static IntPtr Py3UnicodePy2StringtoPtr(string s)
122155
{
123-
return Runtime.IsPython3
124-
? Instance.MarshalManagedToNative(s)
125-
: Marshal.StringToHGlobalAnsi(s);
156+
if (Runtime.IsPython3)
157+
{
158+
int stringBytesCount = PyEncoding.GetByteCount(s);
159+
IntPtr mem = Marshal.AllocHGlobal(stringBytesCount + 4);
160+
fixed (char* str = s)
161+
{
162+
try
163+
{
164+
PyEncoding.GetBytes(str, s.Length, (byte*)mem, stringBytesCount);
165+
}
166+
catch
167+
{
168+
// Do nothing with this. Very strange problem.
169+
}
170+
171+
*(int*)(mem + stringBytesCount) = 0;
172+
}
173+
return mem;
174+
}
175+
176+
return Marshal.StringToHGlobalAnsi(s);
126177
}
127178

128179
/// <summary>
@@ -208,7 +259,12 @@ internal class Utf8Marshaler : MarshalerBase
208259
private static readonly MarshalerBase Instance = new Utf8Marshaler();
209260
private static readonly Encoding PyEncoding = Encoding.UTF8;
210261

211-
public override IntPtr MarshalManagedToNative(object managedObj)
262+
private const int MaxStringLength = 100;
263+
264+
private static readonly EncodedStringsFifoDictionary EncodedStringsDictionary =
265+
new EncodedStringsFifoDictionary(10000, 4 * (MaxStringLength + 1));
266+
267+
public override unsafe IntPtr MarshalManagedToNative(object managedObj)
212268
{
213269
var s = managedObj as string;
214270

@@ -217,21 +273,49 @@ public override IntPtr MarshalManagedToNative(object managedObj)
217273
return IntPtr.Zero;
218274
}
219275

220-
byte[] bStr = PyEncoding.GetBytes(s + "\0");
221-
IntPtr mem = Marshal.AllocHGlobal(bStr.Length);
222-
try
276+
IntPtr mem;
277+
int stringBytesCount;
278+
if (s.Length <= MaxStringLength)
223279
{
224-
Marshal.Copy(bStr, 0, mem, bStr.Length);
280+
if (EncodedStringsDictionary.TryGetValue(s, out mem))
281+
{
282+
return mem;
283+
}
284+
285+
stringBytesCount = PyEncoding.GetByteCount(s);
286+
mem = EncodedStringsDictionary.AddUnsafe(s);
225287
}
226-
catch (Exception)
288+
else
227289
{
228-
Marshal.FreeHGlobal(mem);
229-
throw;
290+
stringBytesCount = PyEncoding.GetByteCount(s);
291+
mem = Marshal.AllocHGlobal(stringBytesCount + 1);
292+
}
293+
294+
fixed (char* str = s)
295+
{
296+
try
297+
{
298+
PyEncoding.GetBytes(str, s.Length, (byte*)mem, stringBytesCount);
299+
}
300+
catch
301+
{
302+
// Do nothing with this. Very strange problem.
303+
}
304+
305+
((byte*)mem)[stringBytesCount] = 0;
230306
}
231307

232308
return mem;
233309
}
234310

311+
public override void CleanUpNativeData(IntPtr pNativeData)
312+
{
313+
if (!EncodedStringsDictionary.IsKnownPtr(pNativeData))
314+
{
315+
base.CleanUpNativeData(pNativeData);
316+
}
317+
}
318+
235319
public static ICustomMarshaler GetInstance(string cookie)
236320
{
237321
return Instance;

0 commit comments

Comments
 (0)