Skip to content

Commit b2a9a98

Browse files
committed
Tensor: dispose Tensors that have been garbage collected (thread safe) + test
1 parent 2a2a1e1 commit b2a9a98

File tree

6 files changed

+125
-36
lines changed

6 files changed

+125
-36
lines changed

src/TensorFlowNET.Core/Status/Status.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ namespace Tensorflow
2626
/// </summary>
2727
public class Status : IDisposable
2828
{
29-
protected readonly IntPtr _handle;
29+
protected IntPtr _handle;
3030

3131
/// <summary>
3232
/// Error message
@@ -71,7 +71,20 @@ public static implicit operator IntPtr(Status status)
7171

7272
public void Dispose()
7373
{
74-
c_api.TF_DeleteStatus(_handle);
74+
IntPtr h = IntPtr.Zero;
75+
lock (this)
76+
{
77+
h = _handle;
78+
_handle = IntPtr.Zero;
79+
}
80+
if (h != IntPtr.Zero)
81+
c_api.TF_DeleteStatus(h);
82+
GC.SuppressFinalize(this);
83+
}
84+
85+
~Status()
86+
{
87+
Dispose();
7588
}
7689
}
7790
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=tensors/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/TensorFlowNET.Core/Tensors/Tensor.Creation.cs

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,21 @@ public partial class Tensor
3131
/// <summary>
3232
/// true if unmanaged buffer has been freed.
3333
/// </summary>
34-
private bool deallocator_called => _deallocatorArgs.deallocator_called;
34+
private bool _deallocator_called => _deallocatorArgs.deallocator_called;
35+
36+
/// <summary>
37+
/// true if the Tensor was created from a managed array
38+
/// </summary>
39+
private bool _isPinnedArray => _deallocatorArgs.gc_handle != IntPtr.Zero;
40+
41+
/// <summary>
42+
/// This holds values that are used by the unmanaged deallocator callback
43+
/// </summary>
44+
private DeallocatorArgs _deallocatorArgs = new DeallocatorArgs() { gc_handle = IntPtr.Zero };
45+
46+
// note: they must be assigned to a static variable in order to work as unmanaged callbacks
47+
static Deallocator _hGlobalDeallocator = FreeHGlobalMemory;
48+
static Deallocator _gcHandleDeallocator = FreeGCHandle;
3549

3650
public Tensor(IntPtr handle)
3751
{
@@ -94,7 +108,7 @@ public unsafe Tensor(sbyte value, TF_DataType? dType = null)
94108
{
95109
var v = (sbyte*)Marshal.AllocHGlobal(sizeof(sbyte));
96110
*v = value;
97-
_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);
111+
_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);
98112
}
99113

100114
/// <summary>
@@ -120,7 +134,7 @@ public unsafe Tensor(byte value, TF_DataType? dType = null)
120134
{
121135
var v = (byte*)Marshal.AllocHGlobal(sizeof(byte));
122136
*v = value;
123-
_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);
137+
_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);
124138
}
125139

126140
/// <summary>
@@ -146,7 +160,7 @@ public unsafe Tensor(short value, TF_DataType? dType = null)
146160
{
147161
var v = (short*)Marshal.AllocHGlobal(sizeof(short));
148162
*v = value;
149-
_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);
163+
_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);
150164
}
151165

152166
/// <summary>
@@ -172,7 +186,7 @@ public unsafe Tensor(ushort value, TF_DataType? dType = null)
172186
{
173187
var v = (ushort*)Marshal.AllocHGlobal(sizeof(ushort));
174188
*v = value;
175-
_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);
189+
_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);
176190
}
177191

178192
/// <summary>
@@ -198,7 +212,7 @@ public unsafe Tensor(int value, TF_DataType? dType = null)
198212
{
199213
var v = (int*)Marshal.AllocHGlobal(sizeof(int));
200214
*v = value;
201-
_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);
215+
_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);
202216
}
203217

204218
/// <summary>
@@ -224,7 +238,7 @@ public unsafe Tensor(uint value, TF_DataType? dType = null)
224238
{
225239
var v = (uint*)Marshal.AllocHGlobal(sizeof(uint));
226240
*v = value;
227-
_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);
241+
_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);
228242
}
229243

230244
/// <summary>
@@ -250,7 +264,7 @@ public unsafe Tensor(long value, TF_DataType? dType = null)
250264
{
251265
var v = (long*)Marshal.AllocHGlobal(sizeof(long));
252266
*v = value;
253-
_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);
267+
_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);
254268
}
255269

256270
/// <summary>
@@ -276,7 +290,7 @@ public unsafe Tensor(ulong value, TF_DataType? dType = null)
276290
{
277291
var v = (ulong*)Marshal.AllocHGlobal(sizeof(ulong));
278292
*v = value;
279-
_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);
293+
_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);
280294
}
281295

282296
/// <summary>
@@ -302,7 +316,7 @@ public unsafe Tensor(float value, TF_DataType? dType = null)
302316
{
303317
var v = (float*)Marshal.AllocHGlobal(sizeof(float));
304318
*v = value;
305-
_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);
319+
_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);
306320
}
307321

308322
/// <summary>
@@ -328,7 +342,7 @@ public unsafe Tensor(double value, TF_DataType? dType = null)
328342
{
329343
var v = (double*)Marshal.AllocHGlobal(sizeof(double));
330344
*v = value;
331-
_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);
345+
_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);
332346
}
333347

334348
/// <summary>
@@ -354,7 +368,7 @@ public unsafe Tensor(Complex value, TF_DataType? dType = null)
354368
{
355369
var v = (Complex*)Marshal.AllocHGlobal(sizeof(Complex));
356370
*v = value;
357-
_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);
371+
_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);
358372
}
359373
#endif
360374

@@ -444,7 +458,7 @@ private unsafe IntPtr Allocate(NDArray nd, TF_DataType? tensorDType = null)
444458
dims.Length,
445459
dotHandle,
446460
(UIntPtr)buffersize,
447-
hGlobalDeallocator,
461+
_hGlobalDeallocator,
448462
ref _deallocatorArgs);
449463

450464
return tfHandle;
@@ -458,8 +472,7 @@ public Tensor(Operation op, int value_index, TF_DataType dtype)
458472
_id = ops.uid();
459473
}
460474

461-
private bool _isPinnedArray => _deallocatorArgs.gc_handle != IntPtr.Zero;
462-
475+
463476
/// <summary>
464477
/// Creates a new tensor from the given array without copying memory. The array is pinned down and the pointer passed on.
465478
/// </summary>
@@ -516,17 +529,11 @@ protected unsafe IntPtr CreateTensorWithoutCopying(TF_DataType dt, long[] shape,
516529
var gcHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
517530
_deallocatorArgs = new DeallocatorArgs() { gc_handle = GCHandle.ToIntPtr(gcHandle) };
518531
if (shape == null || shape.Length == 0)
519-
return TF_NewTensor(dt, new long[0], 0, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr)(count * element_size), gcHandleDeallocator, ref _deallocatorArgs);
532+
return TF_NewTensor(dt, new long[0], 0, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr)(count * element_size), _gcHandleDeallocator, ref _deallocatorArgs);
520533
else
521-
return TF_NewTensor(dt, shape, shape.Length, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr)(count * element_size), gcHandleDeallocator, ref _deallocatorArgs);
534+
return TF_NewTensor(dt, shape, shape.Length, gcHandle.AddrOfPinnedObject() + start * element_size, (UIntPtr)(count * element_size), _gcHandleDeallocator, ref _deallocatorArgs);
522535
}
523536

524-
private DeallocatorArgs _deallocatorArgs = new DeallocatorArgs() { gc_handle = IntPtr.Zero };
525-
526-
// note: they must be assigned to a static variable in order to work as unmanaged callbacks
527-
static Deallocator hGlobalDeallocator = FreeHGlobalMemory;
528-
static Deallocator gcHandleDeallocator = FreeGCHandle;
529-
530537
[MonoPInvokeCallback(typeof(Deallocator))]
531538
internal static void FreeHGlobalMemory(IntPtr dataPtr, IntPtr len, ref DeallocatorArgs args)
532539
{

src/TensorFlowNET.Core/Tensors/Tensor.cs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,12 +350,35 @@ public override string ToString()
350350

351351
public void Dispose()
352352
{
353-
if (_handle != IntPtr.Zero)
353+
IntPtr h=IntPtr.Zero;
354+
lock (this)
354355
{
355-
c_api.TF_DeleteTensor(_handle);
356-
_handle = IntPtr.Zero;
356+
h = _handle;
357+
_handle=IntPtr.Zero;
357358
}
359+
if (h != IntPtr.Zero)
360+
c_api.TF_DeleteTensor(_handle);
358361
status.Dispose();
362+
GC.SuppressFinalize(this);
363+
}
364+
365+
/// <summary>
366+
/// Dispose the tensor when it gets garbage collected
367+
/// </summary>
368+
~Tensor()
369+
{
370+
Dispose();
371+
}
372+
373+
public bool IsDisposed
374+
{
375+
get
376+
{
377+
lock (this)
378+
{
379+
return _handle == IntPtr.Zero;
380+
}
381+
}
359382
}
360383

361384
}

src/TensorFlowNet.Benchmarks/TensorCreation.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
namespace TensorFlowNet.Benchmark
99
{
10-
[SimpleJob(launchCount: 1, warmupCount: 10, targetCount: 30)]
10+
[SimpleJob(launchCount: 1, warmupCount: 2, targetCount: 10)]
1111
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
1212
public class TensorCreation
1313
{
@@ -16,16 +16,19 @@ public class TensorCreation
1616
[GlobalSetup]
1717
public void Setup()
1818
{
19-
data = new double[1000];
19+
data = new double[10];
2020
}
2121

2222
[Benchmark]
2323
public void TensorFromArray()
2424
{
2525
var g=new Graph();
26-
for (int i = 0; i < 100; i++)
26+
for (int i = 0; i < 1000; i++)
2727
{
2828
var tensor = new Tensor(data);
29+
{
30+
31+
}
2932
}
3033
}
3134

@@ -34,9 +37,12 @@ public void TensorFromArray()
3437
public void TensorFromNDArray()
3538
{
3639
var g = new Graph();
37-
for (int i = 0; i < 100; i++)
40+
for (int i = 0; i < 1000; i++)
3841
{
3942
var tensor = new Tensor(new NDArray(data));
43+
{
44+
45+
}
4046
}
4147
}
4248

test/TensorFlowNET.UnitTest/TensorTest.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,54 @@
55
using System.Linq;
66
using System.Runtime.InteropServices;
77
using System.Text;
8+
using System.Threading;
9+
using System.Threading.Tasks;
810
using Tensorflow;
11+
using static Tensorflow.Python;
912

1013
namespace TensorFlowNET.UnitTest
1114
{
1215
[TestClass]
1316
public class TensorTest : CApiTest
1417
{
15-
/// <summary>
16-
/// Port from c_api_test.cc
17-
/// `TEST(CAPI, AllocateTensor)`
18-
/// </summary>
18+
[TestMethod]
19+
public void TensorDeallocationThreadSafety()
20+
{
21+
var tensors = new Tensor[1000];
22+
foreach (var i in range(1000))
23+
{
24+
tensors[i] = new Tensor(new int[1000]);
25+
}
26+
SemaphoreSlim s = new SemaphoreSlim(0, 2);
27+
SemaphoreSlim s_done = new SemaphoreSlim(0, 2);
28+
29+
var t1 = new Thread(() =>
30+
{
31+
s.Wait();
32+
foreach (var t in tensors)
33+
t.Dispose();
34+
s_done.Release();
35+
});
36+
37+
var t2 = new Thread(() =>
38+
{
39+
s.Wait();
40+
foreach (var t in tensors)
41+
t.Dispose();
42+
s_done.Release();
43+
});
44+
45+
t1.Start();
46+
t2.Start();
47+
s.Release(2);
48+
s_done.Wait();
49+
s_done.Wait();
50+
51+
foreach(var t in tensors)
52+
Assert.IsTrue(t.IsDisposed);
53+
}
54+
55+
1956
[TestMethod]
2057
public void AllocateTensor()
2158
{
@@ -29,6 +66,7 @@ public void AllocateTensor()
2966
t.Dispose();*/
3067
}
3168

69+
3270
/// <summary>
3371
/// Port from c_api_test.cc
3472
/// `TEST(CAPI, MaybeMove)`

0 commit comments

Comments
 (0)