diff --git a/TensorFlow.NET.sln.DotSettings b/TensorFlow.NET.sln.DotSettings new file mode 100644 index 000000000..aba8725cc --- /dev/null +++ b/TensorFlow.NET.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/src/TensorFlowNET.Core/APIs/c_api.cs b/src/TensorFlowNET.Core/APIs/c_api.cs index adf0b86fb..56672173b 100644 --- a/src/TensorFlowNET.Core/APIs/c_api.cs +++ b/src/TensorFlowNET.Core/APIs/c_api.cs @@ -54,6 +54,15 @@ public static string StringPiece(IntPtr handle) public struct DeallocatorArgs { + internal static unsafe c_api.DeallocatorArgs* EmptyPtr; + internal static unsafe IntPtr Empty; + + static unsafe DeallocatorArgs() + { + Empty = new IntPtr(EmptyPtr = (DeallocatorArgs*) Marshal.AllocHGlobal(Marshal.SizeOf())); + *EmptyPtr = new DeallocatorArgs() {gc_handle = IntPtr.Zero, deallocator_called = false}; + } + public bool deallocator_called; public IntPtr gc_handle; } diff --git a/src/TensorFlowNET.Core/Sessions/BaseSession.cs b/src/TensorFlowNET.Core/Sessions/BaseSession.cs index c33681208..b1327339d 100644 --- a/src/TensorFlowNET.Core/Sessions/BaseSession.cs +++ b/src/TensorFlowNET.Core/Sessions/BaseSession.cs @@ -152,7 +152,6 @@ private NDArray[] _do_run(List target_list, List fetch_list, { var feeds = new KeyValuePair[feed_dict.Count]; - var ignoreDispose = new bool[feed_dict.Count]; int i = 0; foreach (var x in feed_dict) { @@ -160,8 +159,9 @@ private NDArray[] _do_run(List target_list, List fetch_list, { switch (x.Value) { - case Tensor v: ignoreDispose[i] = true; feeds[i++] = new KeyValuePair(tensor._as_tf_output(), v); break; + case Tensor v: feeds[i++] = new KeyValuePair(tensor._as_tf_output(), v); break; case NDArray v: feeds[i++] = new KeyValuePair(tensor._as_tf_output(), new Tensor(v, tensor.dtype)); break; + case IntPtr v: feeds[i++] = new KeyValuePair(tensor._as_tf_output(), new Tensor(v)); break; #if _REGEN %types = ["sbyte", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double", "Complex"] %foreach types% @@ -194,7 +194,6 @@ private NDArray[] _do_run(List target_list, List fetch_list, #endif case bool v: feeds[i++] = new KeyValuePair(tensor._as_tf_output(), new Tensor((byte) (v ? 1 : 0), TF_DataType.TF_BOOL)); break; case string v: feeds[i++] = new KeyValuePair(tensor._as_tf_output(), new Tensor(v)); break; - case IntPtr v: feeds[i++] = new KeyValuePair(tensor._as_tf_output(), new Tensor(v)); break; default: throw new NotImplementedException($"feed_dict data type {x.Value?.GetType().Name ?? ""}"); } @@ -203,18 +202,7 @@ private NDArray[] _do_run(List target_list, List fetch_list, var fetches = fetch_list.Select(x => x._as_tf_output()).ToArray(); //var targets = target_list; - try - { - return _call_tf_sessionrun(feeds, fetches, target_list); - } finally - { - for (var idx = 0; idx < feeds.Length; idx++) - { - if (ignoreDispose[idx]) - continue; - feeds[idx].Value.Dispose(); - } - } + return _call_tf_sessionrun(feeds, fetches, target_list); } private unsafe NDArray[] _call_tf_sessionrun(KeyValuePair[] feed_dict, TF_Output[] fetch_list, List target_list) diff --git a/src/TensorFlowNET.Core/Tensors/AllocationType.cs b/src/TensorFlowNET.Core/Tensors/AllocationType.cs new file mode 100644 index 000000000..9f5c8badd --- /dev/null +++ b/src/TensorFlowNET.Core/Tensors/AllocationType.cs @@ -0,0 +1,27 @@ +namespace Tensorflow +{ + /// + /// Used internally to + /// + public enum AllocationType + { + None = 0, + /// + /// Allocation was done by passing in a pointer, might be also holding reference to a C# object. + /// + FromPointer = 1, + /// + /// Allocation was done by calling c_api.TF_AllocateTensor or TF decided it has to copy data during c_api.TF_NewTensor.

+ /// Deallocation is handled solely by Tensorflow. + ///
+ Tensorflow = 2, + /// + /// Allocation was done by Marshal.AllocateHGlobal + /// + Marshal = 3, + /// + /// Allocation was done by GCHandle.Alloc + /// + GCHandle = 4, + } +} \ No newline at end of file diff --git a/src/TensorFlowNET.Core/Tensors/Tensor.Creation.cs b/src/TensorFlowNET.Core/Tensors/Tensor.Creation.cs index 5fa60eff2..42e766569 100644 --- a/src/TensorFlowNET.Core/Tensors/Tensor.Creation.cs +++ b/src/TensorFlowNET.Core/Tensors/Tensor.Creation.cs @@ -28,42 +28,37 @@ limitations under the License. namespace Tensorflow { + [SuppressMessage("ReSharper", "InvokeAsExtensionMethod")] public partial class Tensor { /// - /// true if unmanaged buffer has been freed. + /// When Tensor was created from an object that is managed by C#'s GC - this will hold reference to prevent it from being collected. /// - private bool _deallocator_called => _deallocatorArgs.deallocator_called; + protected object AllocationReferenceHolder; /// - /// true if the Tensor was created from a managed array + /// The handle that was used to allocate this tensor, dependent on . /// - private bool _isPinnedArray => _deallocatorArgs.gc_handle != IntPtr.Zero; + protected object AllocationHandle; /// - /// True only if the Tensor object was created in a way that the Tensor object itself allocated memory or pinned a managed object. - /// False if the Tensor was created from a pointer + /// True if this Tensor holds data allocated by C#. /// - public bool IsMemoryOwner { get; private set; } + public bool IsMemoryOwner => AllocationType >= AllocationType.Marshal; /// - /// This holds values that are used by the unmanaged deallocator callback + /// The allocation method used to create this Tensor. /// - private DeallocatorArgs _deallocatorArgs = new DeallocatorArgs() { gc_handle = IntPtr.Zero }; - - // note: they must be assigned to a static variable in order to work as unmanaged callbacks - private static readonly Deallocator _hGlobalDeallocator = FreeHGlobalMemory; - private static readonly Deallocator _gcHandleDeallocator = FreeGCHandle; - private static readonly Deallocator _nothingDeallocator = FreeNothing; + public AllocationType AllocationType { get; protected set; } /// - /// Create a Tensor object from an existing TF handle + /// Create a Tensor object from an existing TF handle /// - /// + /// Handle to a object. public Tensor(IntPtr handle) { _handle = handle; - IsMemoryOwner = false; + //no need to set AllocationType = AllocationType.None; } /// @@ -71,399 +66,376 @@ public Tensor(IntPtr handle) /// Note: the caller is responsible for freeing the memory. Calling Dispose on this object will dispose the TensorFlow tensor /// but not the memory itself! /// - /// Pointer to unmanaged, fixed or pinned memory which the caller owns + /// Pointer to unmanaged, fixed or pinned memory which the caller owns /// Tensor shape /// TF data type /// Size of the tensor in memory - public Tensor(IntPtr ptr, long[] shape, TF_DataType dType, int num_bytes) + public Tensor(IntPtr data_ptr, long[] shape, TF_DataType dType, int num_bytes) { - _handle = TF_NewTensor(dType, dims: shape, num_dims: shape.Length, data: ptr, len: (UIntPtr)num_bytes, deallocator: _nothingDeallocator, ref _deallocatorArgs); - IsMemoryOwner = false; + unsafe + { + _handle = TF_NewTensor(dType, dims: shape, num_dims: shape.Length, data: data_ptr, len: (UIntPtr) num_bytes); + AllocationType = TF_TensorData(_handle) == data_ptr ? AllocationType.FromPointer : AllocationType.Tensorflow; + } + } + + /// + /// Create a new Tensor from the given unmanaged memory pointer (which must be allocated, fixed or pinned by the caller) + /// Note: the caller is responsible for freeing the memory. Calling Dispose on this object will dispose the TensorFlow tensor + /// but not the memory itself! + /// + /// Pointer to unmanaged, fixed or pinned memory which the caller owns + /// Tensor shape + /// TF data type + /// Size of the tensor in memory + public unsafe Tensor(void* data_ptr, long[] shape, TF_DataType dType, int num_bytes) + { + _handle = TF_NewTensor(dType, dims: shape, num_dims: shape.Length, data: data_ptr, len: (UIntPtr) num_bytes); + AllocationType = TF_TensorData(_handle).ToPointer() == data_ptr ? AllocationType.FromPointer : AllocationType.Tensorflow; } #if _REGEN - %types=["sbyte", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double", "Complex"] + %types = ["sbyte", "bool", "byte", "short", "ushort", "int", "uint", "long", "ulong", "float", "double", "Complex"] %foreach types% /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(#1[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(#1)), new long[]{data.Length}, data, Marshal.SizeOf<#1>()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(#1)), new long[] {data.Length}, data, #(#1=="Complex"|"Marshal.SizeOf()"|"sizeof(#(str(#1)))")); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(#1[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(#1)), shape, data, Marshal.SizeOf<#1>()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(#1)), shape, data, #(#1=="Complex"|"Marshal.SizeOf()"|"sizeof(#(str(#1)))")); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(#1 value, TF_DataType? dType = null) { - var v = (#1*)Marshal.AllocHGlobal(sizeof(#1)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(#1)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(#1), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(#1)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(#1)); + *(#1*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } % #else - - /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(sbyte[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(sbyte)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(sbyte)), new long[] {data.Length}, data, sizeof(sbyte)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(sbyte[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(sbyte)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(sbyte)), shape, data, sizeof(sbyte)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(sbyte value, TF_DataType? dType = null) { - var v = (sbyte*)Marshal.AllocHGlobal(sizeof(sbyte)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(sbyte)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(sbyte), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(sbyte)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(sbyte)); + *(sbyte*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(bool[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(bool)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(bool)), new long[] {data.Length}, data, sizeof(bool)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(bool[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(bool)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(bool)), shape, data, sizeof(bool)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(bool value, TF_DataType? dType = null) { - var v = (bool*)Marshal.AllocHGlobal(sizeof(bool)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(bool)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(bool), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(bool)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(bool)); + *(bool*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(byte[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(byte)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(byte)), new long[] {data.Length}, data, sizeof(byte)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(byte[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(byte)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(byte)), shape, data, sizeof(byte)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(byte value, TF_DataType? dType = null) { - var v = (byte*)Marshal.AllocHGlobal(sizeof(byte)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(byte)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(byte), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(byte)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(byte)); + *(byte*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(short[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(short)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(short)), new long[] {data.Length}, data, sizeof(short)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(short[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(short)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(short)), shape, data, sizeof(short)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(short value, TF_DataType? dType = null) { - var v = (short*)Marshal.AllocHGlobal(sizeof(short)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(short)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(short), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(short)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(short)); + *(short*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(ushort[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(ushort)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(ushort)), new long[] {data.Length}, data, sizeof(ushort)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(ushort[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(ushort)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(ushort)), shape, data, sizeof(ushort)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(ushort value, TF_DataType? dType = null) { - var v = (ushort*)Marshal.AllocHGlobal(sizeof(ushort)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(ushort)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(ushort), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(ushort)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(ushort)); + *(ushort*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(int[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(int)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(int)), new long[] {data.Length}, data, sizeof(int)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(int[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(int)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(int)), shape, data, sizeof(int)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(int value, TF_DataType? dType = null) { - var v = (int*)Marshal.AllocHGlobal(sizeof(int)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(int)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(int), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(int)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(int)); + *(int*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(uint[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(uint)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(uint)), new long[] {data.Length}, data, sizeof(uint)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(uint[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(uint)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(uint)), shape, data, sizeof(uint)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(uint value, TF_DataType? dType = null) { - var v = (uint*)Marshal.AllocHGlobal(sizeof(uint)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(uint)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(uint), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(uint)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(uint)); + *(uint*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(long[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(long)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(long)), new long[] {data.Length}, data, sizeof(long)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(long[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(long)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(long)), shape, data, sizeof(long)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(long value, TF_DataType? dType = null) { - var v = (long*)Marshal.AllocHGlobal(sizeof(long)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(long)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(long), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(long)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(long)); + *(long*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(ulong[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(ulong)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(ulong)), new long[] {data.Length}, data, sizeof(ulong)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(ulong[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(ulong)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(ulong)), shape, data, sizeof(ulong)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(ulong value, TF_DataType? dType = null) { - var v = (ulong*)Marshal.AllocHGlobal(sizeof(ulong)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(ulong)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(ulong), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(ulong)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(ulong)); + *(ulong*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(float[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(float)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(float)), new long[] {data.Length}, data, sizeof(float)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(float[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(float)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(float)), shape, data, sizeof(float)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(float value, TF_DataType? dType = null) { - var v = (float*)Marshal.AllocHGlobal(sizeof(float)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(float)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(float), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(float)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(float)); + *(float*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(double[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(double)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(double)), new long[] {data.Length}, data, sizeof(double)); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(double[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(double)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(double)), shape, data, sizeof(double)); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(double value, TF_DataType? dType = null) { - var v = (double*)Marshal.AllocHGlobal(sizeof(double)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(double)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(double), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(double)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(double)); + *(double*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } /// - /// Create a 1d Tensor from the given linear array and shape + /// Create a 1d Tensor from the given linear array and shape /// public Tensor(Complex[] data, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(Complex)), new long[]{data.Length}, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(Complex)), new long[] {data.Length}, data, Marshal.SizeOf()); } /// - /// Create a N-dimensional Tensor from the given array + /// Create a N-dimensional Tensor from the given array /// public Tensor(Complex[] data, long[] shape, TF_DataType? dType = null) { - _handle = CreateTensorWithoutCopying(dType ?? dtypes.as_dtype(typeof(Complex)), shape, data, Marshal.SizeOf()); - IsMemoryOwner=true; + _handle = CreateTensorFromArray(dType ?? dtypes.as_dtype(typeof(Complex)), shape, data, Marshal.SizeOf()); } /// - /// Create a scalar Tensor from the given value + /// Create a scalar Tensor from the given value /// public unsafe Tensor(Complex value, TF_DataType? dType = null) { - var v = (Complex*)Marshal.AllocHGlobal(sizeof(Complex)); - *v = value; - _handle = TF_NewTensor(dType ?? dtypes.as_dtype(typeof(Complex)), dims:new long[0], num_dims: 0, data: (IntPtr)v, len: (UIntPtr)sizeof(Complex), deallocator: _hGlobalDeallocator, ref _deallocatorArgs); - IsMemoryOwner=true; + _handle = TF_AllocateTensor(dType ?? dtypes.as_dtype(typeof(Complex)), dims: new long[0], num_dims: 0, len: (UIntPtr) sizeof(Complex)); + *(Complex*) TF_TensorData(_handle) = value; + AllocationType = AllocationType.Tensorflow; } #endif @@ -474,13 +446,13 @@ public unsafe Tensor(string str) { var status = new Status(); var buffer = Encoding.UTF8.GetBytes(str); - var size = c_api.TF_StringEncodedSize((UIntPtr)buffer.Length); - var handle = TF_AllocateTensor(TF_DataType.TF_STRING, IntPtr.Zero, 0, (UIntPtr)((ulong)size + 8)); + var size = c_api.TF_StringEncodedSize((UIntPtr) buffer.Length); + var handle = TF_AllocateTensor(TF_DataType.TF_STRING, IntPtr.Zero, 0, (UIntPtr) ((ulong) size + 8)); IntPtr tensor = c_api.TF_TensorData(handle); Marshal.WriteInt64(tensor, 0); fixed (byte* src = buffer) - c_api.TF_StringEncode(src, (UIntPtr)buffer.Length, (sbyte*)(tensor + sizeof(Int64)), size, status); + c_api.TF_StringEncode(src, (UIntPtr) buffer.Length, (sbyte*) (tensor + sizeof(Int64)), size, status); _handle = handle; status.Check(true); } @@ -492,7 +464,7 @@ public unsafe Tensor(NDArray nd, TF_DataType? tensorDType = null) { if (nd.Unsafe.Storage.Shape.IsContiguous) { - var bytesLength = (UIntPtr)nd.size; + var bytesLength = (UIntPtr) nd.size; var size = c_api.TF_StringEncodedSize(bytesLength); var handle = TF_AllocateTensor(TF_DataType.TF_STRING, IntPtr.Zero, 0, (UIntPtr) ((ulong) size + 8)); @@ -504,9 +476,7 @@ public unsafe Tensor(NDArray nd, TF_DataType? tensorDType = null) status.Check(true); _handle = handle; - IsMemoryOwner = false; - } - else + } else { var buffer = nd.ToArray(); var size = c_api.TF_StringEncodedSize((UIntPtr) buffer.Length); @@ -521,7 +491,6 @@ public unsafe Tensor(NDArray nd, TF_DataType? tensorDType = null) status.Check(true); _handle = handle; - IsMemoryOwner = false; } return; @@ -532,27 +501,27 @@ public unsafe Tensor(NDArray nd, TF_DataType? tensorDType = null) private unsafe IntPtr CreateTensorFromNDArray(NDArray nd, TF_DataType? given_dtype) { - if (nd.dtype.Name == "String") + if (nd.typecode == NPTypeCode.String) throw new NotImplementedException("Support for NDArray of type string not implemented yet"); - IArraySlice arraySlice; - if (nd.Unsafe.Storage.Shape.IsContiguous == false) - { - // the memory is NOT contiguous, so we have to copy the view into a contiguous memory block. - arraySlice = nd.CloneData(); - } - else + + var arraySlice = nd.Unsafe.Storage.Shape.IsContiguous ? nd.GetData() : nd.CloneData(); + + var handle = TF_NewTensor( + given_dtype ?? nd.dtype.as_dtype(), + dims: nd.shape.Select(i => (long) i).ToArray(), + num_dims: nd.ndim, + data: arraySlice.Address, + len: (UIntPtr) (nd.size * nd.dtypesize)); + + //if TF decided not to perform copy, hold reference for given NDArray. + if (TF_TensorData(handle).ToPointer() == nd.Unsafe.Address) { - // the memory is contiguous - arraySlice = nd.GetData(); - } - this.Tag = arraySlice; // keep a reference to the memory block to make sure it is not disposed while TF is using it - var ptr = new IntPtr(arraySlice.Address); - int num_bytes = (nd.size * nd.dtypesize); - var dtype = given_dtype ?? nd.dtype.as_dtype(); - var handle = TF_NewTensor(dtype, dims: nd.shape.Select(i=>(long)i).ToArray(), num_dims: nd.ndim, data: ptr, len: (UIntPtr)num_bytes, deallocator: _nothingDeallocator, ref _deallocatorArgs); - IsMemoryOwner = false; - return handle; + AllocationType = AllocationType.FromPointer; + AllocationReferenceHolder = nd.Unsafe.Storage; + } else + AllocationType = AllocationType.Tensorflow; + return handle; } public unsafe Tensor(byte[][] buffer, long[] shape) @@ -560,11 +529,12 @@ public unsafe Tensor(byte[][] buffer, long[] shape) int size = 0; foreach (var b in buffer) { - size += (int)TF_StringEncodedSize((UIntPtr)b.Length); + size += (int) TF_StringEncodedSize((UIntPtr) b.Length); } + int totalSize = size + buffer.Length * 8; ulong offset = 0; - IntPtr handle = TF_AllocateTensor(TF_DataType.TF_STRING, shape, shape.Length, (UIntPtr)totalSize); + IntPtr handle = TF_AllocateTensor(TF_DataType.TF_STRING, shape, shape.Length, (UIntPtr) totalSize); // Clear offset table IntPtr pOffset = TF_TensorData(handle); @@ -572,15 +542,15 @@ public unsafe Tensor(byte[][] buffer, long[] shape) IntPtr dstLimit = pOffset + totalSize; for (int i = 0; i < buffer.Length; i++) { - Marshal.WriteInt64(pOffset, (long)offset); + Marshal.WriteInt64(pOffset, (long) offset); using (var status = new Status()) { fixed (byte* src = &buffer[i][0]) { - var written = TF_StringEncode(src, (UIntPtr)buffer[i].Length, (sbyte*)dst, (UIntPtr)(dstLimit.ToInt64() - dst.ToInt64()), status); + var written = TF_StringEncode(src, (UIntPtr) buffer[i].Length, (sbyte*) dst, (UIntPtr) (dstLimit.ToInt64() - dst.ToInt64()), status); status.Check(true); pOffset += 8; - dst += (int)written; + dst += (int) written; offset += written; } } @@ -612,24 +582,26 @@ public Tensor(Operation op, int value_index, TF_DataType dtype) /// [MethodImpl(MethodImplOptions.AggressiveInlining)] [SuppressMessage("ReSharper", "LocalVariableHidesMember")] - protected unsafe IntPtr CreateTensorWithoutCopying(TF_DataType dt, long[] shape, Array data, int element_size) + protected unsafe IntPtr CreateTensorFromArray(TF_DataType dt, long[] shape, Array data, int element_size) { if (dt == TF_DataType.TF_STRING && data is byte[] buffer) { - var size = c_api.TF_StringEncodedSize((UIntPtr)buffer.Length); - var handle = TF_AllocateTensor(TF_DataType.TF_STRING, IntPtr.Zero, 0, (UIntPtr)((ulong)size + 8)); + var size = c_api.TF_StringEncodedSize((UIntPtr) buffer.Length); + var handle = TF_AllocateTensor(TF_DataType.TF_STRING, IntPtr.Zero, 0, (UIntPtr) ((ulong) size + 8)); + AllocationType = AllocationType.Tensorflow; IntPtr tensor = c_api.TF_TensorData(handle); Marshal.WriteInt64(tensor, 0); var status = new Status(); fixed (byte* src = buffer) - c_api.TF_StringEncode(src, (UIntPtr)buffer.Length, (sbyte*)(tensor + sizeof(Int64)), size, status); + c_api.TF_StringEncode(src, (UIntPtr) buffer.Length, (sbyte*) (tensor + sizeof(Int64)), size, status); status.Check(true); return handle; } - return CreateTensorWithoutCopying(dt, shape, data, 0, data.Length, element_size); + + return CreateTensorFromArray(dt, shape, data, 0, data.Length, element_size); } /// @@ -647,67 +619,19 @@ protected unsafe IntPtr CreateTensorWithoutCopying(TF_DataType dt, long[] shape, /// specified dimensions. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected unsafe IntPtr CreateTensorWithoutCopying(TF_DataType dt, long[] shape, Array data, int start, int count, int element_size) + protected IntPtr CreateTensorFromArray(TF_DataType dt, long[] shape, Array data, int start, int count, int element_size) { if (start < 0 || start > data.Length - count) throw new ArgumentException($"Array length {data.Length} does not match the given shape {new Shape(shape.Cast().ToArray())}"); // get a handle to the pinned array which we will pass on to the tensor computation engine to use var gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned); - _deallocatorArgs = new DeallocatorArgs() { gc_handle = GCHandle.ToIntPtr(gcHandle) }; - if (shape == null || shape.Length == 0) - return TF_NewTensor(dt, new long[0], 0, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr)(count * element_size), _gcHandleDeallocator, ref _deallocatorArgs); - else - return TF_NewTensor(dt, shape, shape.Length, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr)(count * element_size), _gcHandleDeallocator, ref _deallocatorArgs); - } - - [MonoPInvokeCallback(typeof(Deallocator))] - internal static void FreeHGlobalMemory(IntPtr dataPtr, IntPtr len, ref DeallocatorArgs args) - { - if (args.deallocator_called || dataPtr == IntPtr.Zero) - return; - - // NumSharp will dispose - Marshal.FreeHGlobal(dataPtr); - args.deallocator_called = true; - } + AllocationType = AllocationType.GCHandle; + AllocationHandle = gcHandle; - [MonoPInvokeCallback(typeof(Deallocator))] - internal static void FreeGCHandle(IntPtr dataPtr, IntPtr len, ref DeallocatorArgs args) - { - if (args.deallocator_called || args.gc_handle == IntPtr.Zero) - return; - // note: since the ptr given to tensorflow is just the addr of the pinned object we can not directly free it! we need to free the gcHandle instead - GCHandle.FromIntPtr(args.gc_handle).Free(); - args.deallocator_called = true; - } - - [MonoPInvokeCallback(typeof(Deallocator))] - internal static void FreeNothing(IntPtr dataPtr, IntPtr len, ref DeallocatorArgs args) - { - args.deallocator_called = true; + if (shape == null || shape.Length == 0) + return TF_NewTensor(dt, new long[0], 0, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr) (count * element_size)); + return TF_NewTensor(dt, shape, shape.Length, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr) (count * element_size)); } - } - - /// - /// This attribute can be applied to callback functions that will be invoked - /// from unmanaged code to managed code. - /// - /// - /// - /// [TensorFlow.MonoPInvokeCallback (typeof (BufferReleaseFunc))] - /// internal static void MyFreeFunc (IntPtr data, IntPtr length){..} - /// - /// - public sealed class MonoPInvokeCallbackAttribute : Attribute - { - /// - /// Use this constructor to annotate the type of the callback function that - /// will be invoked from unmanaged code. - /// - /// T. - public MonoPInvokeCallbackAttribute(Type t) { } - } - -} +} \ No newline at end of file diff --git a/src/TensorFlowNET.Core/Tensors/Tensor.cs b/src/TensorFlowNET.Core/Tensors/Tensor.cs index 75cba69eb..aa2dc6d5f 100644 --- a/src/TensorFlowNET.Core/Tensors/Tensor.cs +++ b/src/TensorFlowNET.Core/Tensors/Tensor.cs @@ -555,9 +555,35 @@ public override string ToString() return $"tf.Tensor '{name}' shape=({string.Join(",", shape)}) dtype={dtype}"; } + /// + /// Dispose any managed resources. + /// + /// Equivalent to what you would perform inside + protected override void DisposeManagedResources() + { + AllocationReferenceHolder = null; + } + + [SuppressMessage("ReSharper", "ConvertIfStatementToSwitchStatement")] protected override void DisposeUnmanagedResources(IntPtr handle) { c_api.TF_DeleteTensor(handle); + + if (AllocationHandle == null) + return; + + if (AllocationType == AllocationType.GCHandle) + { + ((GCHandle) AllocationHandle).Free(); + AllocationHandle = null; + AllocationType = AllocationType.None; + } else if (AllocationType == AllocationType.Marshal) + { + Marshal.FreeHGlobal((IntPtr) AllocationHandle); + AllocationHandle = null; + AllocationType = AllocationType.None; + } else + throw new InvalidOperationException($"Tensor.AllocationHandle is not null ({AllocationHandle}) but AllocationType is not matched to a C# allocation type ({AllocationType})."); } public bool IsDisposed => _disposed; diff --git a/src/TensorFlowNET.Core/Tensors/c_api.tensor.cs b/src/TensorFlowNET.Core/Tensors/c_api.tensor.cs index 6b20b34ff..be5f39326 100644 --- a/src/TensorFlowNET.Core/Tensors/c_api.tensor.cs +++ b/src/TensorFlowNET.Core/Tensors/c_api.tensor.cs @@ -15,6 +15,7 @@ limitations under the License. ******************************************************************************/ using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Tensorflow @@ -77,6 +78,51 @@ public partial class c_api [DllImport(TensorFlowLibName)] public static extern IntPtr TF_NewTensor(TF_DataType dataType, long[] dims, int num_dims, IntPtr data, UIntPtr len, Deallocator deallocator, ref DeallocatorArgs deallocator_arg); + /// + /// Return a new tensor that holds the bytes data[0,len-1] + /// + /// + /// + /// + /// + /// num_bytes, ex: 6 * sizeof(float) + /// + /// + /// + [DllImport(TensorFlowLibName)] + public static extern IntPtr TF_NewTensor(TF_DataType dataType, long[] dims, int num_dims, IntPtr data, UIntPtr len, Deallocator deallocator, IntPtr deallocator_arg); + + /// + /// Return a new tensor that holds the bytes data[0,len-1] + /// + /// + /// + /// + /// + /// num_bytes, ex: 6 * sizeof(float) + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe IntPtr TF_NewTensor(TF_DataType dataType, long[] dims, int num_dims, IntPtr data, UIntPtr len) + { + return TF_NewTensor(dataType, dims, num_dims, data, len, EmptyDeallocator, DeallocatorArgs.Empty); + } + /// + /// Return a new tensor that holds the bytes data[0,len-1] + /// + /// + /// + /// + /// + /// num_bytes, ex: 6 * sizeof(float) + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static unsafe IntPtr TF_NewTensor(TF_DataType dataType, long[] dims, int num_dims, void* data, UIntPtr len) + { + return TF_NewTensor(dataType, dims, num_dims, new IntPtr(data), len); + } + /// /// Return the number of dimensions that the tensor has. /// @@ -159,5 +205,32 @@ public partial class c_api [DllImport(TensorFlowLibName)] public static extern unsafe UIntPtr TF_StringDecode(byte* src, UIntPtr src_len, byte** dst, UIntPtr* dst_len, IntPtr status); + + + public static c_api.Deallocator EmptyDeallocator = FreeNothingDeallocator; + + [MonoPInvokeCallback(typeof(c_api.Deallocator))] + private static void FreeNothingDeallocator(IntPtr dataPtr, IntPtr len, ref c_api.DeallocatorArgs args) + { } + + /// + /// This attribute can be applied to callback functions that will be invoked + /// from unmanaged code to managed code. + /// + /// + /// + /// [TensorFlow.MonoPInvokeCallback (typeof (BufferReleaseFunc))] + /// internal static void MyFreeFunc (IntPtr data, IntPtr length){..} + /// + /// + public sealed class MonoPInvokeCallbackAttribute : Attribute + { + /// + /// Use this constructor to annotate the type of the callback function that + /// will be invoked from unmanaged code. + /// + /// T. + public MonoPInvokeCallbackAttribute(Type t) { } + } } } diff --git a/test/TensorFlowNET.UnitTest/TensorTest.cs b/test/TensorFlowNET.UnitTest/TensorTest.cs index 11557f148..42e03a1ea 100644 --- a/test/TensorFlowNET.UnitTest/TensorTest.cs +++ b/test/TensorFlowNET.UnitTest/TensorTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Runtime.InteropServices; using System.Threading; +using FluentAssertions; using Tensorflow; using static Tensorflow.Binding; @@ -12,77 +13,63 @@ namespace TensorFlowNET.UnitTest [TestClass] public class TensorTest : CApiTest { - [Ignore("Not for mult-thread")] - public void TensorDeallocationThreadSafety() - { - var tensors = new Tensor[1000]; - foreach (var i in range(1000)) - { - tensors[i] = new Tensor(new int[1000]); - } - SemaphoreSlim s = new SemaphoreSlim(0, 2); - SemaphoreSlim s_done = new SemaphoreSlim(0, 2); - - var t1 = new Thread(() => - { - s.Wait(); - foreach (var t in tensors) - t.Dispose(); - s_done.Release(); - }); - - var t2 = new Thread(() => - { - s.Wait(); - foreach (var t in tensors) - t.Dispose(); - s_done.Release(); - }); - - t1.Start(); - t2.Start(); - s.Release(2); - s_done.Wait(); - s_done.Wait(); - - foreach (var t in tensors) - Assert.IsTrue(t.IsDisposed); - } - [TestMethod] public unsafe void TensorFromFixed() { var array = new float[1000]; var span = new Span(array, 100, 500); - fixed (float* ptr=&MemoryMarshal.GetReference(span)) + fixed (float* ptr = &MemoryMarshal.GetReference(span)) { - using (var t = new Tensor((IntPtr)ptr, new long[] {span.Length}, tf.float32, 4*span.Length)) + using (var t = new Tensor((IntPtr) ptr, new long[] {span.Length}, tf.float32, 4 * span.Length)) { Assert.IsFalse(t.IsDisposed); - Assert.IsFalse(t.IsMemoryOwner); Assert.AreEqual(2000, (int) t.bytesize); } } + fixed (float* ptr = &array[0]) { - using (var t = new Tensor((IntPtr)ptr, new long[] { array.Length }, tf.float32, 4 * array.Length)) + using (var t = new Tensor((IntPtr) ptr, new long[] {array.Length}, tf.float32, 4 * array.Length)) { Assert.IsFalse(t.IsDisposed); - Assert.IsFalse(t.IsMemoryOwner); - Assert.AreEqual(4000, (int)t.bytesize); + Assert.AreEqual(4000, (int) t.bytesize); } } } + [TestMethod] + public unsafe void TensorFromArray() + { + var array = new float[1000]; + using (var t = new Tensor(array, new long[] {array.Length}, tf.float32)) + { + Assert.IsFalse(t.IsDisposed); + Assert.AreEqual(1000 * sizeof(float), (int) t.bytesize); + } + + using (var t = new Tensor(new float[] {1}, new long[] {1}, tf.float32)) + { + Assert.IsFalse(t.IsDisposed); + Assert.AreEqual(1 * sizeof(float), (int) t.bytesize); + } + + using (var t = new Tensor(new float[] {1}, null, tf.float32)) + { + Assert.IsFalse(t.IsDisposed); + Assert.AreEqual(1 * sizeof(float), (int) t.bytesize); + t.shape.Should().BeEmpty(); + } + } + [TestMethod] public void AllocateTensor() { ulong num_bytes = 6 * sizeof(float); - long[] dims = { 2, 3 }; + long[] dims = {2, 3}; Tensor t = c_api.TF_AllocateTensor(TF_DataType.TF_FLOAT, dims, 2, num_bytes); EXPECT_EQ(TF_DataType.TF_FLOAT, t.dtype); EXPECT_EQ(2, t.NDims); - EXPECT_EQ((int)dims[0], t.shape[0]); + EXPECT_EQ((int) dims[0], t.shape[0]); EXPECT_EQ(num_bytes, t.bytesize); t.Dispose(); } @@ -98,7 +85,7 @@ public void MaybeMove() NDArray nd = np.array(2, 3); Tensor t = new Tensor(nd); Tensor o = t.MaybeMove(); - ASSERT_TRUE(o == IntPtr.Zero); // It is unsafe to move memory TF might not own. + ASSERT_TRUE(o == IntPtr.Zero); // It is unsafe to move memory TF might not own. t.Dispose(); } @@ -116,10 +103,10 @@ public void Tensor() EXPECT_EQ(tensor.dtype, TF_DataType.TF_FLOAT); EXPECT_EQ(tensor.rank, nd.ndim); - EXPECT_EQ((int)tensor.shape[0], nd.shape[0]); - EXPECT_EQ((int)tensor.shape[1], nd.shape[1]); - EXPECT_EQ(tensor.bytesize, (ulong)nd.size * sizeof(float)); - Assert.IsTrue(Enumerable.SequenceEqual(nd.Data(), new float[] { 1, 2, 3, 4, 5, 6 })); + EXPECT_EQ((int) tensor.shape[0], nd.shape[0]); + EXPECT_EQ((int) tensor.shape[1], nd.shape[1]); + EXPECT_EQ(tensor.bytesize, (ulong) nd.size * sizeof(float)); + Assert.IsTrue(Enumerable.SequenceEqual(nd.Data(), new float[] {1, 2, 3, 4, 5, 6})); } /// @@ -148,7 +135,7 @@ public void SetShape() EXPECT_EQ(-1, num_dims); // Set the shape to be 2 x Unknown - long[] dims = { 2, -1 }; + long[] dims = {2, -1}; c_api.TF_GraphSetTensorShape(graph, feed_out_0, dims, dims.Length, s); Assert.IsTrue(s.Code == TF_Code.TF_OK); num_dims = c_api.TF_GraphGetTensorNumDims(graph, feed_out_0, s); @@ -177,8 +164,8 @@ public void SetShape() c_api.TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s); Assert.IsTrue(s.Code == TF_Code.TF_OK); EXPECT_EQ(2, num_dims); - EXPECT_EQ(2, (int)returned_dims[0]); - EXPECT_EQ(3, (int)returned_dims[1]); + EXPECT_EQ(2, (int) returned_dims[0]); + EXPECT_EQ(3, (int) returned_dims[1]); // Try to set 'unknown' with same rank on the shape and see that // it doesn't change. @@ -189,8 +176,8 @@ public void SetShape() c_api.TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, num_dims, s); Assert.IsTrue(s.Code == TF_Code.TF_OK); EXPECT_EQ(2, num_dims); - EXPECT_EQ(2, (int)returned_dims[0]); - EXPECT_EQ(3, (int)returned_dims[1]); + EXPECT_EQ(2, (int) returned_dims[0]); + EXPECT_EQ(3, (int) returned_dims[1]); // Try to fetch a shape with the wrong num_dims c_api.TF_GraphGetTensorShape(graph, feed_out_0, returned_dims, 5, s); @@ -216,4 +203,4 @@ public void SetShape() s.Dispose(); } } -} +} \ No newline at end of file