blob: fcb0a1e7c235279bab2213388746870b80e90de7 [file] [log] [blame]
telsoa014fcda012018-03-09 14:13:49 +00001//
2// Copyright © 2017 Arm Ltd. All rights reserved.
3// See LICENSE file in the project root for full license information.
4//
5#include <boost/test/unit_test.hpp>
6
7#include "armnn/TypesUtils.hpp"
8
9#include "armnn/IRuntime.hpp"
10#include "armnn/INetwork.hpp"
11#include "armnn/Descriptors.hpp"
12#include "Runtime.hpp"
surmeh013537c2c2018-05-18 16:31:43 +010013#include "HeapProfiling.hpp"
14#include "LeakChecking.hpp"
telsoa014fcda012018-03-09 14:13:49 +000015
16#ifdef WITH_VALGRIND
17#include "valgrind/memcheck.h"
18#endif
19
telsoa014fcda012018-03-09 14:13:49 +000020namespace armnn
21{
22
23void RuntimeLoadedNetworksReserve(armnn::Runtime* runtime)
24{
25 runtime->m_LoadedNetworks.reserve(1);
26}
27
28}
29
30BOOST_AUTO_TEST_SUITE(Runtime)
31
32BOOST_AUTO_TEST_CASE(RuntimeUnloadNetwork)
33{
34 // build 2 mock-networks and load them into the runtime
35 armnn::IRuntimePtr runtime(armnn::IRuntime::Create(armnn::Compute::CpuRef));
36
37 // mock network 1
38 armnn::NetworkId networkIdentifier1 = 1;
39 armnn::INetworkPtr mockNetwork1(armnn::INetwork::Create());
40 mockNetwork1->AddInputLayer(0, "test layer");
41 runtime->LoadNetwork(networkIdentifier1, Optimize(*mockNetwork1, runtime->GetDeviceSpec()));
42
43 // mock network 2
44 armnn::NetworkId networkIdentifier2 = 2;
45 armnn::INetworkPtr mockNetwork2(armnn::INetwork::Create());
46 mockNetwork2->AddInputLayer(0, "test layer");
47 runtime->LoadNetwork(networkIdentifier2, Optimize(*mockNetwork2, runtime->GetDeviceSpec()));
48
49 // unload one by its networkID
50 BOOST_TEST(runtime->UnloadNetwork(networkIdentifier1) == armnn::Status::Success);
51
52 BOOST_TEST(runtime->UnloadNetwork(networkIdentifier1) == armnn::Status::Failure);
53}
54
surmeh013537c2c2018-05-18 16:31:43 +010055// Note: the current builds we don't do valgrind and gperftools based leak checking at the same
56// time, so in practice WITH_VALGRIND and ARMNN_LEAK_CHECKING_ENABLED are exclusive. In
57// the future the gperftools based leak checking should stay and the valgrind based should
58// be removed.
59
60#if ARMNN_LEAK_CHECKING_ENABLED
61void CreateAndDropDummyNetwork(armnn::Runtime & runtime)
62{
63 armnn::NetworkId networkIdentifier;
64 {
65 armnn::TensorInfo inputTensorInfo(armnn::TensorShape({ 7, 7 }), armnn::DataType::Float32);
66 armnn::TensorInfo outputTensorInfo(armnn::TensorShape({ 7, 7 }), armnn::DataType::Float32);
67
68 armnn::INetworkPtr network(armnn::INetwork::Create());
69
70 armnn::IConnectableLayer* input = network->AddInputLayer(0, "input");
71 armnn::IConnectableLayer* layer = network->AddActivationLayer(armnn::ActivationDescriptor(), "test");
72 armnn::IConnectableLayer* output = network->AddOutputLayer(0, "output");
73
74 input->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
75 layer->GetOutputSlot(0).Connect(output->GetInputSlot(0));
76
77 // set the tensors in the network
78 input->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
79 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
80
81 // optimize the network
82 armnn::IOptimizedNetworkPtr optNet = Optimize(*network, runtime.GetDeviceSpec());
83
84 runtime.LoadNetwork(networkIdentifier, std::move(optNet));
85 }
86
87 runtime.UnloadNetwork(networkIdentifier);
88}
89
90BOOST_AUTO_TEST_CASE(RuntimeHeapMemoryUsageSanityChecks)
91{
92 BOOST_TEST(ARMNN_LEAK_CHECKER_IS_ACTIVE());
93 {
94 ARMNN_SCOPED_LEAK_CHECKER("Sanity_Check_Outer");
95 {
96 ARMNN_SCOPED_LEAK_CHECKER("Sanity_Check_Inner");
97 std::unique_ptr<char[]> dummyAllocation(new char[1000]);
98 BOOST_TEST(ARMNN_NO_LEAKS_IN_SCOPE() == false);
99 BOOST_TEST(ARMNN_BYTES_LEAKED_IN_SCOPE() >= 1000);
100 BOOST_TEST(ARMNN_OBJECTS_LEAKED_IN_SCOPE() >= 1);
101 }
102 BOOST_TEST(ARMNN_NO_LEAKS_IN_SCOPE());
103 BOOST_TEST(ARMNN_BYTES_LEAKED_IN_SCOPE() == 0);
104 BOOST_TEST(ARMNN_OBJECTS_LEAKED_IN_SCOPE() == 0);
105 }
106}
107
108#ifdef ARMCOMPUTECL_ENABLED
109BOOST_AUTO_TEST_CASE(RuntimeMemoryLeaksGpuAcc)
110{
111 BOOST_TEST(ARMNN_LEAK_CHECKER_IS_ACTIVE());
112
113 armnn::Runtime runtime(armnn::Compute::GpuAcc);
114 armnn::RuntimeLoadedNetworksReserve(&runtime);
115
116 {
117 // Do a warmup of this so we make sure that all one-time
118 // initialization happens before we do the leak checking.
119 CreateAndDropDummyNetwork(runtime);
120 }
121
122 {
123 ARMNN_SCOPED_LEAK_CHECKER("LoadAndUnloadNetworkGpuAcc");
124 // In the second run we check for all remaining memory
125 // in use after the network was unloaded. If there is any
126 // then it will be treated as a memory leak.
127 CreateAndDropDummyNetwork(runtime);
128 BOOST_TEST(ARMNN_NO_LEAKS_IN_SCOPE());
129 BOOST_TEST(ARMNN_BYTES_LEAKED_IN_SCOPE() == 0);
130 BOOST_TEST(ARMNN_OBJECTS_LEAKED_IN_SCOPE() == 0);
131 }
132}
133#endif // ARMCOMPUTECL_ENABLED
134
135#ifdef ARMCOMPUTENEON_ENABLED
136BOOST_AUTO_TEST_CASE(RuntimeMemoryLeaksCpuAcc)
137{
138 BOOST_TEST(ARMNN_LEAK_CHECKER_IS_ACTIVE());
139
140 armnn::Runtime runtime(armnn::Compute::CpuAcc);
141 armnn::RuntimeLoadedNetworksReserve(&runtime);
142
143 {
144 // Do a warmup of this so we make sure that all one-time
145 // initialization happens before we do the leak checking.
146 CreateAndDropDummyNetwork(runtime);
147 }
148
149 {
150 ARMNN_SCOPED_LEAK_CHECKER("LoadAndUnloadNetworkCpuAcc");
151 // In the second run we check for all remaining memory
152 // in use after the network was unloaded. If there is any
153 // then it will be treated as a memory leak.
154 CreateAndDropDummyNetwork(runtime);
155 BOOST_TEST(ARMNN_NO_LEAKS_IN_SCOPE());
156 BOOST_TEST(ARMNN_BYTES_LEAKED_IN_SCOPE() == 0);
157 BOOST_TEST(ARMNN_OBJECTS_LEAKED_IN_SCOPE() == 0);
158 }
159}
160#endif // ARMCOMPUTENEON_ENABLED
161
162BOOST_AUTO_TEST_CASE(RuntimeMemoryLeaksCpuRef)
163{
164 BOOST_TEST(ARMNN_LEAK_CHECKER_IS_ACTIVE());
165
166 armnn::Runtime runtime(armnn::Compute::CpuRef);
167 armnn::RuntimeLoadedNetworksReserve(&runtime);
168
169 {
170 // Do a warmup of this so we make sure that all one-time
171 // initialization happens before we do the leak checking.
172 CreateAndDropDummyNetwork(runtime);
173 }
174
175 {
176 ARMNN_SCOPED_LEAK_CHECKER("LoadAndUnloadNetworkCpuRef");
177 // In the second run we check for all remaining memory
178 // in use after the network was unloaded. If there is any
179 // then it will be treated as a memory leak.
180 CreateAndDropDummyNetwork(runtime);
181 BOOST_TEST(ARMNN_NO_LEAKS_IN_SCOPE());
182 BOOST_TEST(ARMNN_BYTES_LEAKED_IN_SCOPE() == 0);
183 BOOST_TEST(ARMNN_OBJECTS_LEAKED_IN_SCOPE() == 0);
184 }
185}
186
187#endif // ARMNN_LEAK_CHECKING_ENABLED
188
189// Note: this part of the code is due to be removed when we fully trust the gperftools based results.
telsoa014fcda012018-03-09 14:13:49 +0000190#if defined(ARMCOMPUTECL_ENABLED) && defined(WITH_VALGRIND)
191BOOST_AUTO_TEST_CASE(RuntimeMemoryUsage)
192{
193 // From documentation:
194
195 // This means that no pointer to the block can be found. The block is classified as "lost",
196 // because the programmer could not possibly have freed it at program exit, since no pointer to it exists.
197 unsigned long leakedBefore = 0;
198 unsigned long leakedAfter = 0;
199
200 // A start-pointer or chain of start-pointers to the block is found. Since the block is still pointed at,
201 // the programmer could, at least in principle, have freed it before program exit.
202 // We want to test this in case memory is not freed as early as it could have been
203 unsigned long reachableBefore = 0;
204 unsigned long reachableAfter = 0;
205
206 // needed as out params but we don't test them
207 unsigned long dubious = 0;
208 unsigned long suppressed = 0;
209
210 // ensure that runtime is large enough before checking for memory leaks
211 // otherwise when loading the network it will automatically reserve memory that won't be released until destruction
212 armnn::NetworkId networkIdentifier;
213 armnn::Runtime runtime(armnn::Compute::GpuAcc);
214 armnn::RuntimeLoadedNetworksReserve(&runtime);
215
216 // check for leaks before we load the network and record them so that we can see the delta after unloading
217 VALGRIND_DO_QUICK_LEAK_CHECK;
218 VALGRIND_COUNT_LEAKS(leakedBefore, dubious, reachableBefore, suppressed);
219
220 // build a mock-network and load it into the runtime
221 {
222 armnn::TensorInfo inputTensorInfo(armnn::TensorShape({ 7, 7 }), armnn::DataType::Float32);
223 armnn::TensorInfo outputTensorInfo(armnn::TensorShape({ 7, 7 }), armnn::DataType::Float32);
224
225 armnn::INetworkPtr mockNetwork(armnn::INetwork::Create());
226
227 armnn::IConnectableLayer* input = mockNetwork->AddInputLayer(0, "input");
228 armnn::IConnectableLayer* layer = mockNetwork->AddActivationLayer(armnn::ActivationDescriptor(), "test");
229 armnn::IConnectableLayer* output = mockNetwork->AddOutputLayer(0, "output");
230
231 input->GetOutputSlot(0).Connect(layer->GetInputSlot(0));
232 layer->GetOutputSlot(0).Connect(output->GetInputSlot(0));
233
234 // set the tensors in the network
235 input->GetOutputSlot(0).SetTensorInfo(inputTensorInfo);
236 layer->GetOutputSlot(0).SetTensorInfo(outputTensorInfo);
237
238 // optimize the network
239 armnn::IOptimizedNetworkPtr optNet = Optimize(*mockNetwork, runtime.GetDeviceSpec());
240
241 runtime.LoadNetwork(networkIdentifier, std::move(optNet));
242 }
243
244 runtime.UnloadNetwork(networkIdentifier);
245
246 VALGRIND_DO_ADDED_LEAK_CHECK;
247 VALGRIND_COUNT_LEAKS(leakedAfter, dubious, reachableAfter, suppressed);
248
249 // if we're not running under Valgrind, these vars will have been initialised to 0, so this will always pass
250 BOOST_TEST(leakedBefore == leakedAfter);
251
252 // Add resonable threshold after and before running valgrind with the ACL clear cache function.
surmeh013537c2c2018-05-18 16:31:43 +0100253 // TODO Threshold set to 80k until the root cause of the memory leakage is found and fixed. Revert threshold
254 // value to 1024 when fixed
255 BOOST_TEST(static_cast<long>(reachableAfter) - static_cast<long>(reachableBefore) < 81920);
telsoa014fcda012018-03-09 14:13:49 +0000256
257 // these are needed because VALGRIND_COUNT_LEAKS is a macro that assigns to the parameters
258 // so they are assigned to, but still considered unused, causing a warning
259 boost::ignore_unused(dubious);
260 boost::ignore_unused(suppressed);
261}
262#endif
263
surmeh013537c2c2018-05-18 16:31:43 +0100264// Note: this part of the code is due to be removed when we fully trust the gperftools based results.
telsoa014fcda012018-03-09 14:13:49 +0000265#ifdef WITH_VALGRIND
266// run with the following command to get all the amazing output (in the devenv/build folder) :)
267// valgrind --leak-check=full --show-leak-kinds=all --log-file=Valgrind_Memcheck_Leak_Report.txt armnn/test/UnitTests
268BOOST_AUTO_TEST_CASE(RuntimeMemoryLeak)
269{
270 // From documentation:
271
272 // This means that no pointer to the block can be found. The block is classified as "lost",
273 // because the programmer could not possibly have freed it at program exit, since no pointer to it exists.
274 unsigned long leakedBefore = 0;
275 unsigned long leakedAfter = 0;
276
277 // A start-pointer or chain of start-pointers to the block is found. Since the block is still pointed at,
278 // the programmer could, at least in principle, have freed it before program exit.
279 // We want to test this in case memory is not freed as early as it could have been
280 unsigned long reachableBefore = 0;
281 unsigned long reachableAfter = 0;
282
283 // needed as out params but we don't test them
284 unsigned long dubious = 0;
285 unsigned long suppressed = 0;
286
287 armnn::NetworkId networkIdentifier1 = 1;
288
289 // ensure that runtime is large enough before checking for memory leaks
290 // otherwise when loading the network it will automatically reserve memory that won't be released until destruction
291 armnn::Runtime runtime(armnn::Compute::CpuRef);
292 armnn::RuntimeLoadedNetworksReserve(&runtime);
293
294 // check for leaks before we load the network and record them so that we can see the delta after unloading
295 VALGRIND_DO_QUICK_LEAK_CHECK;
296 VALGRIND_COUNT_LEAKS(leakedBefore, dubious, reachableBefore, suppressed);
297
298 // build a mock-network and load it into the runtime
299 {
300 unsigned int inputShape[] = {1, 7, 1, 1};
301 armnn::TensorInfo inputTensorInfo(4, inputShape, armnn::DataType::Float32);
302
303 std::unique_ptr<armnn::Network> mockNetwork1 = std::make_unique<armnn::Network>();
304 mockNetwork1->AddInputLayer(0, "test layer");
305
306 armnn::DeviceSpec device;
307 device.DefaultComputeDevice = armnn::Compute::CpuRef;
308
309 runtime.LoadNetwork(networkIdentifier1, Optimize(*mockNetwork1, device));
310 }
311
312 runtime.UnloadNetwork(networkIdentifier1);
313
314 VALGRIND_DO_ADDED_LEAK_CHECK;
315 VALGRIND_COUNT_LEAKS(leakedAfter, dubious, reachableAfter, suppressed);
316
317 // if we're not running under Valgrind, these vars will have been initialised to 0, so this will always pass
318 BOOST_TEST(leakedBefore == leakedAfter);
surmeh01bceff2f2018-03-29 16:29:27 +0100319
320 #if defined(ARMCOMPUTECL_ENABLED)
321 // reachableBefore == reachableAfter should hold, but on OpenCL with Android we are still
322 // not entirely able to control the memory in the OpenCL driver. Testing is showing that
323 // after this test (which clears all OpenCL memory) we are clearing a little bit more than
324 // we expect, probably depending on the order in which other tests are run.
325 BOOST_TEST(reachableBefore - reachableAfter <= 24);
326 #else
327 BOOST_TEST(reachableBefore == reachableAfter);
328 #endif
329
330 BOOST_TEST(reachableBefore >= reachableAfter);
telsoa014fcda012018-03-09 14:13:49 +0000331
332 // these are needed because VALGRIND_COUNT_LEAKS is a macro that assigns to the parameters
333 // so they are assigned to, but still considered unused, causing a warning
334 boost::ignore_unused(dubious);
335 boost::ignore_unused(suppressed);
336}
337#endif
338
339BOOST_AUTO_TEST_SUITE_END()