diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17c1780ee..1658a6f95 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,7 +8,8 @@ This document follows the conventions laid out in [Keep a CHANGELOG][].
## [unreleased][]
### Added
-
+- Improved performance. String marshaling between python and clr now cached.
+ Cache reduces GC pressure and saves from extensive memory copying.
- Added support for embedding python into dotnet core 2.0 (NetStandard 2.0)
- Added new build system (pythonnet.15.sln) based on dotnetcore-sdk/xplat(crossplatform msbuild).
Currently there two side-by-side build systems that produces the same output (net40) from the same sources.
diff --git a/src/embed_tests/Python.EmbeddingTest.csproj b/src/embed_tests/Python.EmbeddingTest.csproj
index 6aa48becc..caffa7256 100644
--- a/src/embed_tests/Python.EmbeddingTest.csproj
+++ b/src/embed_tests/Python.EmbeddingTest.csproj
@@ -104,6 +104,7 @@
+
diff --git a/src/embed_tests/TestPythonEngineProperties.cs b/src/embed_tests/TestPythonEngineProperties.cs
index 243349b82..d95942577 100644
--- a/src/embed_tests/TestPythonEngineProperties.cs
+++ b/src/embed_tests/TestPythonEngineProperties.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using NUnit.Framework;
using Python.Runtime;
diff --git a/src/embed_tests/TestRuntime.cs b/src/embed_tests/TestRuntime.cs
index ac1fa1ac0..aeef28135 100644
--- a/src/embed_tests/TestRuntime.cs
+++ b/src/embed_tests/TestRuntime.cs
@@ -34,7 +34,7 @@ public static void PlatformCache()
// Don't shut down the runtime: if the python engine was initialized
// but not shut down by another test, we'd end up in a bad state.
- }
+ }
[Test]
public static void Py_IsInitializedValue()
diff --git a/src/embed_tests/TestsSuite.cs b/src/embed_tests/TestsSuite.cs
new file mode 100644
index 000000000..44ce2d4b8
--- /dev/null
+++ b/src/embed_tests/TestsSuite.cs
@@ -0,0 +1,18 @@
+using NUnit.Framework;
+using Python.Runtime;
+
+namespace Python.EmbeddingTest
+{
+ [SetUpFixture]
+ public class TestsSuite
+ {
+ [OneTimeTearDown]
+ public void FinalCleanup()
+ {
+ if (PythonEngine.IsInitialized)
+ {
+ PythonEngine.Shutdown();
+ }
+ }
+ }
+}
diff --git a/src/runtime/CustomMarshaler.cs b/src/runtime/CustomMarshaler.cs
index b51911816..507710f87 100644
--- a/src/runtime/CustomMarshaler.cs
+++ b/src/runtime/CustomMarshaler.cs
@@ -18,7 +18,7 @@ public object MarshalNativeToManaged(IntPtr pNativeData)
public abstract IntPtr MarshalManagedToNative(object managedObj);
- public void CleanUpNativeData(IntPtr pNativeData)
+ public virtual void CleanUpNativeData(IntPtr pNativeData)
{
Marshal.FreeHGlobal(pNativeData);
}
@@ -44,7 +44,12 @@ internal class UcsMarshaler : MarshalerBase
private static readonly MarshalerBase Instance = new UcsMarshaler();
private static readonly Encoding PyEncoding = Runtime.PyEncoding;
- public override IntPtr MarshalManagedToNative(object managedObj)
+ private const int MaxStringLength = 100;
+ private const int MaxItemSize = 4 * (MaxStringLength + 1);
+ private static readonly EncodedStringsFifoDictionary EncodedStringsDictionary =
+ new EncodedStringsFifoDictionary(10000, MaxItemSize);
+
+ public override unsafe IntPtr MarshalManagedToNative(object managedObj)
{
var s = managedObj as string;
@@ -53,16 +58,36 @@ public override IntPtr MarshalManagedToNative(object managedObj)
return IntPtr.Zero;
}
- byte[] bStr = PyEncoding.GetBytes(s + "\0");
- IntPtr mem = Marshal.AllocHGlobal(bStr.Length);
- try
+ IntPtr mem;
+ int stringBytesCount;
+ if (s.Length <= MaxStringLength)
{
- Marshal.Copy(bStr, 0, mem, bStr.Length);
+ if (EncodedStringsDictionary.TryGetValue(s, out mem))
+ {
+ return mem;
+ }
+
+ stringBytesCount = PyEncoding.GetByteCount(s);
+ mem = EncodedStringsDictionary.AddUnsafe(s);
}
- catch (Exception)
+ else
{
- Marshal.FreeHGlobal(mem);
- throw;
+ stringBytesCount = PyEncoding.GetByteCount(s);
+ mem = Marshal.AllocHGlobal(stringBytesCount + 4);
+ }
+
+ fixed (char* str = s)
+ {
+ try
+ {
+ PyEncoding.GetBytes(str, s.Length, (byte*)mem, stringBytesCount);
+ }
+ catch
+ {
+ // Do nothing with this. Very strange problem.
+ }
+
+ *(int*)(mem + stringBytesCount) = 0;
}
return mem;
@@ -106,6 +131,14 @@ public static int GetUnicodeByteLength(IntPtr p)
}
}
+ public override void CleanUpNativeData(IntPtr pNativeData)
+ {
+ if (!EncodedStringsDictionary.IsKnownPtr(pNativeData))
+ {
+ base.CleanUpNativeData(pNativeData);
+ }
+ }
+
///
/// Utility function for Marshaling Unicode on PY3 and AnsiStr on PY2.
/// Use on functions whose Input signatures changed between PY2/PY3.
@@ -118,11 +151,29 @@ public static int GetUnicodeByteLength(IntPtr p)
///
/// You MUST deallocate the IntPtr of the Return when done with it.
///
- public static IntPtr Py3UnicodePy2StringtoPtr(string s)
+ public unsafe static IntPtr Py3UnicodePy2StringtoPtr(string s)
{
- return Runtime.IsPython3
- ? Instance.MarshalManagedToNative(s)
- : Marshal.StringToHGlobalAnsi(s);
+ if (Runtime.IsPython3)
+ {
+ int stringBytesCount = PyEncoding.GetByteCount(s);
+ IntPtr mem = Marshal.AllocHGlobal(stringBytesCount + 4);
+ fixed (char* str = s)
+ {
+ try
+ {
+ PyEncoding.GetBytes(str, s.Length, (byte*)mem, stringBytesCount);
+ }
+ catch
+ {
+ // Do nothing with this. Very strange problem.
+ }
+
+ *(int*)(mem + stringBytesCount) = 0;
+ }
+ return mem;
+ }
+
+ return Marshal.StringToHGlobalAnsi(s);
}
///
@@ -208,7 +259,12 @@ internal class Utf8Marshaler : MarshalerBase
private static readonly MarshalerBase Instance = new Utf8Marshaler();
private static readonly Encoding PyEncoding = Encoding.UTF8;
- public override IntPtr MarshalManagedToNative(object managedObj)
+ private const int MaxStringLength = 100;
+
+ private static readonly EncodedStringsFifoDictionary EncodedStringsDictionary =
+ new EncodedStringsFifoDictionary(10000, 4 * (MaxStringLength + 1));
+
+ public override unsafe IntPtr MarshalManagedToNative(object managedObj)
{
var s = managedObj as string;
@@ -217,21 +273,49 @@ public override IntPtr MarshalManagedToNative(object managedObj)
return IntPtr.Zero;
}
- byte[] bStr = PyEncoding.GetBytes(s + "\0");
- IntPtr mem = Marshal.AllocHGlobal(bStr.Length);
- try
+ IntPtr mem;
+ int stringBytesCount;
+ if (s.Length <= MaxStringLength)
{
- Marshal.Copy(bStr, 0, mem, bStr.Length);
+ if (EncodedStringsDictionary.TryGetValue(s, out mem))
+ {
+ return mem;
+ }
+
+ stringBytesCount = PyEncoding.GetByteCount(s);
+ mem = EncodedStringsDictionary.AddUnsafe(s);
}
- catch (Exception)
+ else
{
- Marshal.FreeHGlobal(mem);
- throw;
+ stringBytesCount = PyEncoding.GetByteCount(s);
+ mem = Marshal.AllocHGlobal(stringBytesCount + 1);
+ }
+
+ fixed (char* str = s)
+ {
+ try
+ {
+ PyEncoding.GetBytes(str, s.Length, (byte*)mem, stringBytesCount);
+ }
+ catch
+ {
+ // Do nothing with this. Very strange problem.
+ }
+
+ ((byte*)mem)[stringBytesCount] = 0;
}
return mem;
}
+ public override void CleanUpNativeData(IntPtr pNativeData)
+ {
+ if (!EncodedStringsDictionary.IsKnownPtr(pNativeData))
+ {
+ base.CleanUpNativeData(pNativeData);
+ }
+ }
+
public static ICustomMarshaler GetInstance(string cookie)
{
return Instance;
diff --git a/src/runtime/Python.Runtime.csproj b/src/runtime/Python.Runtime.csproj
index fc155ca91..00a290988 100644
--- a/src/runtime/Python.Runtime.csproj
+++ b/src/runtime/Python.Runtime.csproj
@@ -76,6 +76,12 @@
+
+
+
+
+
+
Properties\SharedAssemblyInfo.cs
diff --git a/src/runtime/assemblymanager.cs b/src/runtime/assemblymanager.cs
index 3085bb639..039d44951 100644
--- a/src/runtime/assemblymanager.cs
+++ b/src/runtime/assemblymanager.cs
@@ -27,6 +27,7 @@ internal class AssemblyManager
// So for multidomain support it is better to have the dict. recreated for each app-domain initialization
private static ConcurrentDictionary> namespaces =
new ConcurrentDictionary>();
+
//private static Dictionary> generics;
private static AssemblyLoadEventHandler lhandler;
private static ResolveEventHandler rhandler;
diff --git a/src/runtime/perf_utils/EncodedStringsFifoDictionary.cs b/src/runtime/perf_utils/EncodedStringsFifoDictionary.cs
new file mode 100644
index 000000000..74a88f0ec
--- /dev/null
+++ b/src/runtime/perf_utils/EncodedStringsFifoDictionary.cs
@@ -0,0 +1,73 @@
+using System;
+
+namespace Python.Runtime
+{
+ using System.Runtime.InteropServices;
+
+ public class EncodedStringsFifoDictionary: IDisposable
+ {
+ private readonly FifoDictionary _innerDictionary;
+
+ private readonly IntPtr _rawMemory;
+
+ private readonly int _allocatedSize;
+
+ public EncodedStringsFifoDictionary(int capacity, int maxItemSize)
+ {
+ if (maxItemSize < 1)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(maxItemSize),
+ "Maximum item size should be non-zero positive.");
+ }
+
+ _innerDictionary = new FifoDictionary(capacity);
+ _allocatedSize = maxItemSize * capacity;
+ _rawMemory = Marshal.AllocHGlobal(_allocatedSize);
+
+ MaxItemSize = maxItemSize;
+ }
+
+ public int MaxItemSize { get; }
+
+ public bool TryGetValue(string key, out IntPtr value)
+ {
+ return _innerDictionary.TryGetValue(key, out value);
+ }
+
+ public IntPtr AddUnsafe(string key)
+ {
+ int nextSlot = _innerDictionary.NextSlotToAdd;
+ IntPtr ptr = _rawMemory + (MaxItemSize * nextSlot);
+ _innerDictionary.AddUnsafe(key, ptr);
+ return ptr;
+ }
+
+ public bool IsKnownPtr(IntPtr ptr)
+ {
+ var uptr = (ulong)ptr;
+ var umem = (ulong)_rawMemory;
+
+ return uptr >= umem && uptr < umem + (ulong)_allocatedSize;
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ if (_rawMemory != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(_rawMemory);
+ }
+ }
+
+ public void Dispose()
+ {
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ ~EncodedStringsFifoDictionary()
+ {
+ ReleaseUnmanagedResources();
+ }
+ }
+}
diff --git a/src/runtime/perf_utils/EncodingGetStringPolyfill.cs b/src/runtime/perf_utils/EncodingGetStringPolyfill.cs
new file mode 100644
index 000000000..ac1d0ddcf
--- /dev/null
+++ b/src/runtime/perf_utils/EncodingGetStringPolyfill.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace Python.Runtime
+{
+#if !NETSTANDARD
+ ///
+ /// This polyfill is thread unsafe.
+ ///
+ [CLSCompliant(false)]
+ public static class EncodingGetStringPolyfill
+ {
+ private static readonly MethodInfo PlatformGetStringMethodInfo =
+ typeof(Encoding).GetMethod(
+ "GetString",
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null,
+ new[]
+ {
+ typeof(byte*), typeof(int)
+ }, null);
+
+ private static readonly byte[] StdDecodeBuffer = PlatformGetStringMethodInfo == null ? new byte[1024 * 1024] : null;
+
+ private static Dictionary PlatformGetStringMethodsDelegatesCache = new Dictionary();
+
+ private unsafe delegate string EncodingGetStringUnsafeDelegate(byte* pstr, int size);
+
+ public unsafe static string GetString(this Encoding encoding, byte* pstr, int size)
+ {
+ if (PlatformGetStringMethodInfo != null)
+ {
+ EncodingGetStringUnsafeDelegate getStringDelegate;
+ if (!PlatformGetStringMethodsDelegatesCache.TryGetValue(encoding, out getStringDelegate))
+ {
+ getStringDelegate =
+ (EncodingGetStringUnsafeDelegate)Delegate.CreateDelegate(
+ typeof(EncodingGetStringUnsafeDelegate), encoding, PlatformGetStringMethodInfo);
+ PlatformGetStringMethodsDelegatesCache.Add(encoding, getStringDelegate);
+ }
+ return getStringDelegate(pstr, size);
+ }
+
+ byte[] buffer = size <= StdDecodeBuffer.Length ? StdDecodeBuffer : new byte[size];
+ Marshal.Copy((IntPtr)pstr, buffer, 0, size);
+ return encoding.GetString(buffer, 0, size);
+ }
+ }
+#endif
+
+}
diff --git a/src/runtime/perf_utils/FifoDictionary.cs b/src/runtime/perf_utils/FifoDictionary.cs
new file mode 100644
index 000000000..3af127de1
--- /dev/null
+++ b/src/runtime/perf_utils/FifoDictionary.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace Python.Runtime
+{
+ using System.Collections.Generic;
+
+ public class FifoDictionary
+ {
+ private readonly Dictionary _innerDictionary;
+
+ private readonly KeyValuePair[] _fifoList;
+
+ private bool _hasEmptySlots = true;
+
+ public FifoDictionary(int capacity)
+ {
+ if (capacity <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(capacity), "Capacity should be non-zero positive.");
+ }
+
+ _innerDictionary = new Dictionary(capacity);
+ _fifoList = new KeyValuePair[capacity];
+
+ Capacity = capacity;
+ }
+
+ public bool TryGetValue(TKey key, out TValue value)
+ {
+ int index;
+ if (_innerDictionary.TryGetValue(key, out index))
+ {
+ value =_fifoList[index].Value;
+ return true;
+ }
+
+ value = default(TValue);
+ return false;
+ }
+
+ public void AddUnsafe(TKey key, TValue value)
+ {
+ if (!_hasEmptySlots)
+ {
+ _innerDictionary.Remove(_fifoList[NextSlotToAdd].Key);
+ }
+
+ _innerDictionary.Add(key, NextSlotToAdd);
+ _fifoList[NextSlotToAdd] = new KeyValuePair(key, value);
+
+ NextSlotToAdd++;
+ if (NextSlotToAdd >= Capacity)
+ {
+ _hasEmptySlots = false;
+ NextSlotToAdd = 0;
+ }
+ }
+
+ public int NextSlotToAdd { get; private set; }
+ public int Capacity { get; }
+ }
+}
diff --git a/src/runtime/perf_utils/RawImmutableMemBlock.cs b/src/runtime/perf_utils/RawImmutableMemBlock.cs
new file mode 100644
index 000000000..6230bf5ec
--- /dev/null
+++ b/src/runtime/perf_utils/RawImmutableMemBlock.cs
@@ -0,0 +1,84 @@
+namespace Python.Runtime
+{
+ using System;
+
+ public struct RawImmutableMemBlock: IEquatable
+ {
+ private readonly int _hash;
+
+ public RawImmutableMemBlock(IntPtr ptr, int size)
+ {
+ if (ptr == IntPtr.Zero)
+ {
+ throw new ArgumentException("Memory pointer should not be zero", nameof(ptr));
+ }
+
+ if (size < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(size), "Size should be zero or positive.");
+ }
+
+ Ptr = ptr;
+ Size = size;
+ _hash = RawMemUtils.FastXorHash(ptr, size);
+ }
+
+ public RawImmutableMemBlock(RawImmutableMemBlock memBlock, IntPtr newPtr)
+ {
+ if (memBlock.Ptr == IntPtr.Zero)
+ {
+ throw new ArgumentException("Cannot copy non initialized RawImmutableMemBlock structure.", nameof(memBlock));
+ }
+
+ if (newPtr == IntPtr.Zero)
+ {
+ throw new ArgumentException("Cannot copy to zero pointer.");
+ }
+
+ RawMemUtils.CopyMemBlocks(memBlock.Ptr, newPtr, memBlock.Size);
+ Ptr = newPtr;
+ Size = memBlock.Size;
+ _hash = memBlock._hash;
+ }
+
+ public IntPtr Ptr { get; }
+
+ public int Size { get; }
+
+ public bool Equals(RawImmutableMemBlock other)
+ {
+ bool preEqual = _hash == other._hash && Size == other.Size;
+ if (!preEqual)
+ {
+ return false;
+ }
+
+ return RawMemUtils.CompareMemBlocks(Ptr, other.Ptr, Size);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ return obj is RawImmutableMemBlock && Equals((RawImmutableMemBlock)obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (_hash * 397) ^ Size;
+ }
+ }
+
+ public static bool operator ==(RawImmutableMemBlock left, RawImmutableMemBlock right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(RawImmutableMemBlock left, RawImmutableMemBlock right)
+ {
+ return !left.Equals(right);
+ }
+ }
+}
diff --git a/src/runtime/perf_utils/RawMemUtils.cs b/src/runtime/perf_utils/RawMemUtils.cs
new file mode 100644
index 000000000..694acd1e8
--- /dev/null
+++ b/src/runtime/perf_utils/RawMemUtils.cs
@@ -0,0 +1,140 @@
+namespace Python.Runtime
+{
+ using System;
+
+ public static class RawMemUtils
+ {
+ public static unsafe bool CopyMemBlocks(IntPtr src, IntPtr dest, int size)
+ {
+ // XOR with 64 bit step
+ var p64_1 = (ulong*)src;
+ var p64_2 = (ulong*)dest;
+ int c64count = size >> 3;
+
+ int i = 0;
+ while (i
+ /// Calculating simple 32 bit xor hash for raw memory.
+ ///
+ /// Memory pointer.
+ /// Size to hash.
+ /// 32 bit hash the in signed int format.
+ public static unsafe int FastXorHash(IntPtr mem, int size)
+ {
+ unchecked
+ {
+ // XOR with 64 bit step
+ ulong r64 = 0;
+ var p64 = (ulong*)mem;
+ var pn = (byte*)(mem + (size & ~7));
+ while (p64 < pn)
+ {
+ r64 ^= *p64++;
+ }
+
+ uint r32 = (uint)r64 ^ (uint)(r64 >> 32);
+ if ((size & 4) != 0)
+ {
+ r32 ^= *(uint*)pn;
+ pn += 4;
+ }
+
+ if ((size & 2) != 0)
+ {
+ r32 ^= *(ushort*)pn;
+ pn += 2;
+ }
+
+ if ((size & 1) != 0)
+ {
+ r32 ^= *pn;
+ }
+
+ return (int)r32;
+ }
+ }
+ }
+}
diff --git a/src/runtime/perf_utils/RawMemoryFifoDictionary.cs b/src/runtime/perf_utils/RawMemoryFifoDictionary.cs
new file mode 100644
index 000000000..d845271d5
--- /dev/null
+++ b/src/runtime/perf_utils/RawMemoryFifoDictionary.cs
@@ -0,0 +1,59 @@
+namespace Python.Runtime
+{
+ using System;
+ using System.Runtime.InteropServices;
+
+ public class RawMemoryFifoDictionary : IDisposable
+ {
+ private readonly FifoDictionary _innerDictionary;
+
+ private readonly IntPtr _rawMemory;
+
+ public RawMemoryFifoDictionary(int capacity, int maxItemSize)
+ {
+ if (maxItemSize < 1)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(maxItemSize),
+ "Maximum item size should be non-zero positive.");
+ }
+
+ MaxItemSize = maxItemSize;
+ _innerDictionary = new FifoDictionary(capacity);
+ _rawMemory = Marshal.AllocHGlobal(maxItemSize*capacity);
+ }
+
+ ~RawMemoryFifoDictionary()
+ {
+ ReleaseUnmanagedResources();
+ }
+
+ public int MaxItemSize { get; }
+
+ public bool TryGetValue(RawImmutableMemBlock key, out TValue value)
+ {
+ return _innerDictionary.TryGetValue(key, out value);
+ }
+
+ public void AddUnsafe(RawImmutableMemBlock key, TValue value)
+ {
+ int nextSlot = _innerDictionary.NextSlotToAdd;
+ var localKey = new RawImmutableMemBlock(key, _rawMemory + (MaxItemSize * nextSlot));
+ _innerDictionary.AddUnsafe(localKey, value);
+ }
+
+ public void Dispose()
+ {
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ if (_rawMemory != IntPtr.Zero)
+ {
+ Marshal.FreeHGlobal(_rawMemory);
+ }
+ }
+ }
+}
diff --git a/src/runtime/pyobject.cs b/src/runtime/pyobject.cs
index 7dca85545..02eca3c44 100644
--- a/src/runtime/pyobject.cs
+++ b/src/runtime/pyobject.cs
@@ -6,6 +6,9 @@
namespace Python.Runtime
{
+ using System.Reflection;
+ using System.Threading;
+
///
/// Represents a generic Python object. The methods of this class are
/// generally equivalent to the Python "abstract object API". See
@@ -261,9 +264,34 @@ public PyObject GetAttr(PyObject name)
{
throw new PythonException();
}
+
return new PyObject(op);
}
+ public T GetAttr(string name)
+ {
+ IntPtr op = Runtime.PyObject_GetAttrString(obj, name);
+
+ if (op == IntPtr.Zero)
+ {
+ throw new PythonException();
+ }
+
+ try
+ {
+ object resultObj;
+ if (!Converter.ToManaged(op, typeof(T), out resultObj, false))
+ {
+ throw new InvalidCastException("cannot convert object to target type");
+ }
+
+ return (T)resultObj;
+ }
+ finally
+ {
+ Runtime.XDecref(op);
+ }
+ }
///
/// GetAttr Method
@@ -1089,6 +1117,12 @@ public override bool TryInvoke(InvokeBinder binder, object[] args, out object re
public override bool TryConvert(ConvertBinder binder, out object result)
{
+ if (typeof(PyObject).IsAssignableFrom(binder.Type))
+ {
+ result = this;
+ return true;
+ }
+
return Converter.ToManaged(this.obj, binder.Type, out result, false);
}
diff --git a/src/runtime/runtime.cs b/src/runtime/runtime.cs
index d4cb85583..7e93b65fe 100644
--- a/src/runtime/runtime.cs
+++ b/src/runtime/runtime.cs
@@ -266,6 +266,12 @@ public enum MachineType
///
internal static readonly Encoding PyEncoding = _UCS == 2 ? Encoding.Unicode : Encoding.UTF32;
+ ///
+ /// 4Mb of Python strings to .Net strings cache.
+ ///
+ private static readonly RawMemoryFifoDictionary UcsStringsInternDictionary =
+ new RawMemoryFifoDictionary(10000, 100 * _UCS);
+
///
/// Initialize the runtime...
///
@@ -1524,7 +1530,7 @@ internal static IntPtr PyUnicode_FromString(string s)
///
/// PyStringType or PyUnicodeType object to convert
/// Managed String
- internal static string GetManagedString(IntPtr op)
+ internal static unsafe string GetManagedString(IntPtr op)
{
IntPtr type = PyObject_TYPE(op);
@@ -1541,9 +1547,20 @@ internal static string GetManagedString(IntPtr op)
int length = (int)PyUnicode_GetSize(op);
int size = length * _UCS;
- var buffer = new byte[size];
- Marshal.Copy(p, buffer, 0, size);
- return PyEncoding.GetString(buffer, 0, size);
+ if (size <= UcsStringsInternDictionary.MaxItemSize)
+ {
+ var ucsStringMemBlock = new RawImmutableMemBlock(p, size);
+ string str;
+ if (!UcsStringsInternDictionary.TryGetValue(ucsStringMemBlock, out str))
+ {
+ str = PyEncoding.GetString((byte*)p, size);
+ UcsStringsInternDictionary.AddUnsafe(ucsStringMemBlock, str);
+ }
+
+ return str;
+ }
+
+ return PyEncoding.GetString((byte*)p, size);
}
return null;