8000 Merge · csharpHub/pythonnet@2f5c7b2 · GitHub
[go: up one dir, main page]

Skip to content

Commit 2f5c7b2

Browse files
author
dse
committed
Merge
2 parents dab4dcc + 5136c85 commit 2f5c7b2

11 files changed

+639
-26
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
1010
### Added
1111
- Added tool for debugging floating bugs. Stable tests are executed in the loop. ~100 cycles is enough to pop up any bugs.
1212
Usage: Python.EmbeddingTest.exe --loop --where="cat != Unstable"
13+
- Improved performance. String marshaling between python and clr now cached.
14+
Cache reduces GC pressure and saves from extensive memory copying.
1315
- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0)
1416
- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild).
1517
Currently there two side-by-side build systems that produces the same output (net40) from the same sources.

src/runtime/CustomMarshaler.cs

Lines changed: 105 additions & 21 deletions
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;
230 CE7 306
}
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;

src/runtime/Python.Runtime.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@
7676
<Reference Include="System" />
7777
</ItemGroup>
7878
<ItemGroup>
79+
<Compile Include="perf_utils\EncodedStringsFifoDictionary.cs" />
80+
<Compile Include="perf_utils\EncodingGetStringPolyfill.cs" />
81+
<Compile Include="perf_utils\FifoDictionary.cs" />
82+
<Compile Include="perf_utils\RawImmutableMemBlock.cs" />
83+
<Compile Include="perf_utils\RawMemoryFifoDictionary.cs" />
84+
<Compile Include="perf_utils\RawMemUtils.cs" />
7985
<Compile Include="Properties\AssemblyInfo.cs" />
8086
<Compile Include="..\SharedAssemblyInfo.cs">
8187
<Link>Properties\SharedAssemblyInfo.cs</Link>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using System;
2+
3+
namespace Python.Runtime
4+
{
5+
using System.Runtime.InteropServices;
6+
7+
public class EncodedStringsFifoDictionary: IDisposable
8+
{
9+
private readonly FifoDictionary<string, IntPtr> _innerDictionary;
10+
11+
private readonly IntPtr _rawMemory;
12+
13+
private readonly int _allocatedSize;
14+
15+
public EncodedStringsFifoDictionary(int capacity, int maxItemSize)
16+
{
17+
if (maxItemSize < 1)
18+
{
19+
throw new ArgumentOutOfRangeException(
20+
nameof(maxItemSize),
21+
"Maximum item size should be non-zero positive.");
22+
}
23+
24+
_innerDictionary = new FifoDictionary<string, IntPtr>(capacity);
25+
_allocatedSize = maxItemSize * capacity;
26+
_rawMemory = Marshal.AllocHGlobal(_allocatedSize);
27+
28+
MaxItemSize = maxItemSize;
29+
}
30+
31+
public int MaxItemSize { get; }
32+
33+
public bool TryGetValue(string key, out IntPtr value)
34+
{
35+
return _innerDictionary.TryGetValue(key, out value);
36+
}
37+
38+
public IntPtr AddUnsafe(string key)
39+
{
40+
int nextSlot = _innerDictionary.NextSlotToAdd;
41+
IntPtr ptr = _rawMemory + (MaxItemSize * nextSlot);
42+
_innerDictionary.AddUnsafe(key, ptr);
43+
return ptr;
44+
}
45+
46+
public bool IsKnownPtr(IntPtr ptr)
47+
{
48+
var uptr = (ulong)ptr;
49+
var umem = (ulong)_rawMemory;
50+
51+
return uptr >= umem && uptr < umem + (ulong)_allocatedSize;
52+
}
53+
54+
private void ReleaseUnmanagedResources()
55+
{
56+
if (_rawMemory != IntPtr.Zero)
57+
{
58+
Marshal.FreeHGlobal(_rawMemory);
59+
}
60+
}
61+
62+
public void Dispose()
63+
{
64+
ReleaseUnmanagedResources();
65+
GC.SuppressFinalize(this);
66+
}
67+
68+
~EncodedStringsFifoDictionary()
69+
{
70+
ReleaseUnmanagedResources();
71+
}
72+
}
73+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
using System.Runtime.InteropServices;
5+
using System.Text;
6+
7+
namespace Python.Runtime
8+
{
9+
#if !NETSTANDARD
10+
/// <summary>
11+
/// This polyfill is thread unsafe.
12+
/// </summary>
13+
[CLSCompliant(false)]
14+
public static class EncodingGetStringPolyfill
15+
{
16+
private static readonly MethodInfo PlatformGetStringMethodInfo =
17+
typeof(Encoding).GetMethod(
18+
"GetString",
19+
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
20+
new[]
21+
{
22+
typeof(byte*), typeof(int)
23+
}, null);
24+
25+
private static readonly byte[] StdDecodeBuffer = PlatformGetStringMethodInfo == null ? new byte[1024 * 1024] : null;
26+
27+
private static Dictionary<Encoding, EncodingGetStringUnsafeDelegate> PlatformGetStringMethodsDelegatesCache = new Dictionary<Encoding, EncodingGetStringUnsafeDelegate>();
28+
29+
private unsafe delegate string EncodingGetStringUnsafeDelegate(byte* pstr, int size);
30+
31+
public unsafe static string GetString(this Encoding encoding, byte* pstr, int size)
32+
{
33+
if (PlatformGetStringMethodInfo != null)
34+
{
35+
EncodingGetStringUnsafeDelegate getStringDelegate;
36+
if (!PlatformGetStringMethodsDelegatesCache.TryGetValue(encoding, out getStringDelegate))
37+
{
38+
getStringDelegate =
39+
(EncodingGetStringUnsafeDelegate)Delegate.CreateDelegate(
40+
typeof(EncodingGetStringUnsafeDelegate), encoding, PlatformGetStringMethodInfo);
41+
PlatformGetStringMethodsDelegatesCache.Add(encoding, getStringDelegate);
42+
}
43+
return getStringDelegate(pstr, size);
44+
}
45+
46+
byte[] buffer = size <= StdDecodeBuffer.Length ? StdDecodeBuffer : new byte[size];
47+
Marshal.Copy((IntPtr)pstr, buffer, 0, size);
48+
return encoding.GetString(buffer, 0, size);
49+
}
50+
}
51+
#endif
52+
53+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using System;
2+
3+
namespace Python.Runtime
4+
{
5+
using System.Collections.Generic;
6+
7+
public class FifoDictionary<TKey, TValue>
8+
{
9+
private readonly Dictionary<TKey, int> _innerDictionary;
10+
11+
private readonly KeyValuePair<TKey,TValue>[] _fifoList;
12+
13+
private bool _hasEmptySlots = true;
14+
15+
public FifoDictionary(int capacity)
16+
{
17+
if (capacity <= 0)
18+
{
19+
throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity should be non-zero positive.");
20+
}
21+
22+
_innerDictionary = new Dictionary<TKey, int>(capacity);
23+
_fifoList = new KeyValuePair<TKey, TValue>[capacity];
24+
25+
Capacity = capacity;
26+
}
27+
28+
public bool TryGetValue(TKey key, out TValue value)
29+
{
30+
int index;
31+
if (_innerDictionary.TryGetValue(key, out index))
32+
{
33+
value =_fifoList[index].Value;
34+
return true;
35+
}
36+
37+
value = default(TValue);
38+
return false;
39+
}
40+
41+
public void AddUnsafe(TKey key, TValue value)
42+
{
43+
if (!_hasEmptySlots)
44+
{
45+
_innerDictionary.Remove(_fifoList[NextSlotToAdd].Key);
46+
}
47+
48+
_innerDictionary.Add(key, NextSlotToAdd);
49+
_fifoList[NextSlotToAdd] = new KeyValuePair<TKey, TValue>(key, value);
50+
51+
NextSlotToAdd++;
52+
if (NextSlotToAdd >= Capacity)
53+
{
54+
_hasEmptySlots = false;
55+
NextSlotToAdd = 0;
56+
}
57+
}
58+
59+
public int NextSlotToAdd { get; private set; }
60+
public int Capacity { get; }
61+
}
62+
}

0 commit comments

Comments
 (0)
0