1 /**
2 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include <array>
16 #include <atomic>
17 #include <chrono>
18 #include <condition_variable>
19 #include <limits>
20 #include <thread>
21
22 #include "gtest/gtest.h"
23 #include "iostream"
24 #include "libpandabase/utils/utils.h"
25 #include "runtime/include/coretypes/string.h"
26 #include "runtime/include/runtime.h"
27 #include "runtime/include/panda_vm.h"
28 #include "runtime/handle_scope-inl.h"
29 #include "runtime/mem/gc/g1/g1-allocator.h"
30 #include "runtime/mem/gc/generational-gc-base.h"
31 #include "runtime/mem/malloc-proxy-allocator-inl.h"
32 #include "runtime/mem/mem_stats.h"
33 #include "runtime/mem/mem_stats_default.h"
34 #include "runtime/mem/runslots_allocator-inl.h"
35
36 namespace ark::mem::test {
37
38 class G1GCFullGCTest : public testing::Test {
39 public:
40 using ObjVec = PandaVector<ObjectHeader *>;
41 using HanVec = PandaVector<VMHandle<ObjectHeader *> *>;
42 static constexpr size_t ROOT_MAX_SIZE = 100000U;
43
44 static constexpr GCTaskCause MIXED_G1_GC_CAUSE = GCTaskCause::YOUNG_GC_CAUSE;
45 static constexpr GCTaskCause FULL_GC_CAUSE = GCTaskCause::EXPLICIT_CAUSE;
46
47 enum class TargetSpace { YOUNG, TENURED, LARGE, HUMONG };
48
49 class GCCounter : public GCListener {
50 public:
GCStarted(const GCTask & task,size_t heapSize)51 void GCStarted([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSize) override
52 {
53 count_++;
54 }
55
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)56 void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
57 [[maybe_unused]] size_t heapSize) override
58 {
59 }
60
61 private:
62 int count_ = 0;
63 };
64
65 template <typename F>
66 class GCHook : public GCListener {
67 public:
GCHook(F hookArg)68 explicit GCHook(F hookArg) : hook_(hookArg) {};
69
GCStarted(const GCTask & task,size_t heapSize)70 void GCStarted([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSize) override {}
71
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)72 void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
73 [[maybe_unused]] size_t heapSize) override
74 {
75 hook_();
76 }
77
78 private:
79 F hook_;
80 };
81
SetupRuntime(const std::string & gcTypeParam)82 void SetupRuntime(const std::string &gcTypeParam)
83 {
84 RuntimeOptions options;
85 options.SetShouldLoadBootPandaFiles(false);
86 options.SetShouldInitializeIntrinsics(false);
87 options.SetUseTlabForAllocations(false);
88 options.SetGcType(gcTypeParam);
89 options.SetGcTriggerType("debug");
90 options.SetGcDebugTriggerStart(std::numeric_limits<int>::max());
91 options.SetCompilerEnableJit(false);
92 constexpr size_t HEAP_SIZE = 33554432U;
93 options.SetHeapSizeLimit(HEAP_SIZE);
94 constexpr size_t YOUNG_SIZE = 4194304U;
95 options.SetYoungSpaceSize(YOUNG_SIZE);
96 options.SetExplicitConcurrentGcEnabled(false);
97 [[maybe_unused]] bool success = Runtime::Create(options);
98 ASSERT(success);
99
100 thread = ark::MTManagedThread::GetCurrent();
101 gcType = Runtime::GetGCType(options, plugins::RuntimeTypeToLang(Runtime::GetRuntimeType()));
102 [[maybe_unused]] auto gcLocal = thread->GetVM()->GetGC();
103 ASSERT(gcLocal->GetType() == ark::mem::GCTypeFromString(gcTypeParam));
104 ASSERT(gcLocal->IsGenerational());
105 thread->ManagedCodeBegin();
106 }
107
ResetRuntime()108 void ResetRuntime()
109 {
110 DeleteHandles();
111 internalAllocator->Delete(gccnt);
112 thread->ManagedCodeEnd();
113 bool success = Runtime::Destroy();
114 ASSERT_TRUE(success) << "Cannot destroy Runtime";
115 }
116
117 template <typename F, size_t MULTI>
118 ObjVec MakeAllocations(size_t minSize, size_t maxSize, size_t count, size_t *allocated, size_t *requested,
119 F spaceChecker, bool checkOomInTenured = false);
120
121 void InitRoot();
122 void MakeObjectsAlive(const ObjVec &objects, int every = 1);
123 void MakeObjectsPermAlive(const ObjVec &objects, int every = 1);
124 void MakeObjectsGarbage(size_t startIdx, size_t afterEndIdx, int every = 1);
125 void DumpHandles();
126 void DumpAliveObjects();
127 void DeleteHandles();
128 bool IsInYoung(uintptr_t addr);
129
130 template <class LanguageConfig>
131 void PrepareTest();
132
TearDown()133 void TearDown() override {}
134
135 // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
136 ark::MTManagedThread *thread {};
137 GCType gcType {};
138
139 LanguageContext ctx {nullptr};
140 ObjectAllocatorBase *objectAllocator {};
141 mem::InternalAllocatorPtr internalAllocator;
142 PandaVM *vm {};
143 GC *gc {};
144 std::vector<HanVec> handles;
145 MemStatsType *ms {};
146 GCStats *gcMs {};
147 coretypes::Array *root = nullptr;
148 size_t rootSize = 0;
149 GCCounter *gccnt {};
150 // NOLINTEND(misc-non-private-member-variables-in-classes)
151 };
152
153 template <typename F, size_t MULTI>
MakeAllocations(size_t minSize,size_t maxSize,size_t count,size_t * allocated,size_t * requested,F spaceChecker,bool checkOomInTenured)154 G1GCFullGCTest::ObjVec G1GCFullGCTest::MakeAllocations(size_t minSize, size_t maxSize, size_t count, size_t *allocated,
155 size_t *requested, [[maybe_unused]] F spaceChecker,
156 bool checkOomInTenured)
157 {
158 ASSERT(minSize <= maxSize);
159 *allocated = 0;
160 *requested = 0;
161 // Create array of object templates based on count and max size
162 PandaVector<PandaString> objTemplates(count);
163 size_t objSize = sizeof(coretypes::String) + minSize;
164 for (size_t i = 0; i < count; ++i) {
165 PandaString simpleString;
166 simpleString.resize(objSize - sizeof(coretypes::String));
167 objTemplates[i] = std::move(simpleString);
168 objSize += (maxSize / count + i); // +i to mess with the alignment
169 if (objSize > maxSize) {
170 objSize = maxSize;
171 }
172 }
173 ObjVec result;
174 result.reserve(count * MULTI);
175 for (size_t j = 0; j < count; ++j) {
176 size_t size = objTemplates[j].length() + sizeof(coretypes::String);
177 if (checkOomInTenured) {
178 // Leaving 5MB in tenured seems OK
179 auto free =
180 reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
181 constexpr size_t BIG_SIZE = 5000000U;
182 if (size + BIG_SIZE > free) {
183 return result;
184 }
185 }
186 for (size_t i = 0; i < MULTI; ++i) {
187 // create string of '\0's
188 coretypes::String *stringObj =
189 coretypes::String::CreateFromMUtf8(reinterpret_cast<const uint8_t *>(&objTemplates[j][0]),
190 objTemplates[j].length(), objTemplates[j].length(), true, ctx, vm);
191 ASSERT(stringObj != nullptr);
192 ASSERT(stringObj->GetLength() == objTemplates[j].length());
193 ASSERT(spaceChecker(ToUintPtr(stringObj)));
194 *allocated += GetAlignedObjectSize(size);
195 *requested += size;
196 result.push_back(stringObj);
197 }
198 }
199 return result;
200 }
201
InitRoot()202 void G1GCFullGCTest::InitRoot()
203 {
204 ClassLinker *classLinker = Runtime::GetCurrent()->GetClassLinker();
205 Class *klass = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY)
206 ->GetClass(ctx.GetStringArrayClassDescriptor());
207 ASSERT_NE(klass, nullptr);
208 root = coretypes::Array::Create(klass, ROOT_MAX_SIZE);
209 rootSize = 0;
210 MakeObjectsPermAlive({root});
211 }
212
MakeObjectsAlive(const ObjVec & objects,int every)213 void G1GCFullGCTest::MakeObjectsAlive(const ObjVec &objects, int every)
214 {
215 int cnt = every;
216 for (auto *obj : objects) {
217 cnt--;
218 if (cnt != 0) {
219 continue;
220 }
221 root->Set(rootSize, obj);
222 rootSize++;
223 ASSERT(rootSize < ROOT_MAX_SIZE);
224 cnt = every;
225 }
226 }
227
MakeObjectsGarbage(size_t startIdx,size_t afterEndIdx,int every)228 void G1GCFullGCTest::MakeObjectsGarbage(size_t startIdx, size_t afterEndIdx, int every)
229 {
230 int cnt = every;
231 for (size_t i = startIdx; i < afterEndIdx; ++i) {
232 cnt--;
233 if (cnt != 0) {
234 continue;
235 }
236 root->Set(i, static_cast<ObjectHeader *>(nullptr));
237 rootSize++;
238 cnt = every;
239 }
240 }
241
MakeObjectsPermAlive(const ObjVec & objects,int every)242 void G1GCFullGCTest::MakeObjectsPermAlive(const ObjVec &objects, int every)
243 {
244 HanVec result;
245 result.reserve(objects.size() / every);
246 int cnt = every;
247 for (auto *obj : objects) {
248 cnt--;
249 if (cnt != 0) {
250 continue;
251 }
252 result.push_back(internalAllocator->New<VMHandle<ObjectHeader *>>(thread, obj));
253 cnt = every;
254 }
255 handles.push_back(result);
256 }
257
DumpHandles()258 void G1GCFullGCTest::DumpHandles()
259 {
260 for (auto &hv : handles) {
261 for (auto *handle : hv) {
262 std::cout << "vector " << (void *)&hv << " handle " << (void *)handle << " obj " << handle->GetPtr()
263 << std::endl;
264 }
265 }
266 }
267
DumpAliveObjects()268 void G1GCFullGCTest::DumpAliveObjects()
269 {
270 std::cout << "Alive root array : " << handles[0][0]->GetPtr() << std::endl;
271 for (size_t i = 0; i < rootSize; ++i) {
272 if (root->Get<ObjectHeader *>(i) != nullptr) {
273 std::cout << "Alive idx " << i << " : " << root->Get<ObjectHeader *>(i) << std::endl;
274 }
275 }
276 }
277
DeleteHandles()278 void G1GCFullGCTest::DeleteHandles()
279 {
280 for (auto &hv : handles) {
281 for (auto *handle : hv) {
282 internalAllocator->Delete(handle);
283 }
284 }
285 handles.clear();
286 }
287
288 template <class LanguageConfig>
PrepareTest()289 void G1GCFullGCTest::PrepareTest()
290 {
291 if constexpr (std::is_same<LanguageConfig, ark::PandaAssemblyLanguageConfig>::value) {
292 DeleteHandles();
293 ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
294 objectAllocator = thread->GetVM()->GetGC()->GetObjectAllocator();
295 vm = Runtime::GetCurrent()->GetPandaVM();
296 internalAllocator = Runtime::GetCurrent()->GetClassLinker()->GetAllocator();
297 gc = vm->GetGC();
298 ms = vm->GetMemStats();
299 gcMs = vm->GetGCStats();
300 gccnt = internalAllocator->New<GCCounter>();
301 gc->AddListener(gccnt);
302 InitRoot();
303 } else {
304 UNREACHABLE(); // NYI
305 }
306 }
307
IsInYoung(uintptr_t addr)308 bool G1GCFullGCTest::IsInYoung(uintptr_t addr)
309 {
310 switch (gcType) {
311 case GCType::GEN_GC: {
312 return objectAllocator->IsObjectInYoungSpace(reinterpret_cast<ObjectHeader *>(addr));
313 }
314 case GCType::G1_GC: {
315 auto memPool = PoolManager::GetMmapMemPool();
316 if (memPool->GetSpaceTypeForAddr(reinterpret_cast<ObjectHeader *>(addr)) != SpaceType::SPACE_TYPE_OBJECT) {
317 return false;
318 }
319 return AddrToRegion(ToVoidPtr(addr))->HasFlag(RegionFlag::IS_EDEN);
320 }
321 default:
322 UNREACHABLE(); // NYI
323 }
324 return false;
325 }
326
TEST_F(G1GCFullGCTest,TestIntensiveAlloc)327 TEST_F(G1GCFullGCTest, TestIntensiveAlloc)
328 {
329 std::string gctype = static_cast<std::string>(GCStringFromType(GCType::G1_GC));
330 SetupRuntime(gctype);
331 {
332 HandleScope<ObjectHeader *> scope(thread);
333 PrepareTest<ark::PandaAssemblyLanguageConfig>();
334 [[maybe_unused]] size_t bytes {};
335 [[maybe_unused]] size_t rawObjectsSize {};
336
337 [[maybe_unused]] size_t youngSize =
338 reinterpret_cast<GenerationalSpaces *>(
339 reinterpret_cast<ObjectAllocatorGenBase *>(objectAllocator)->GetHeapSpace())
340 ->GetCurrentYoungSize();
341 [[maybe_unused]] size_t heapSize = mem::MemConfig::GetHeapSizeLimit();
342 [[maybe_unused]] auto g1Alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(objectAllocator);
343 [[maybe_unused]] size_t maxYSize = g1Alloc->GetYoungAllocMaxSize();
344
345 [[maybe_unused]] auto ySpaceCheck = [this](uintptr_t addr) -> bool { return IsInYoung(addr); };
346 [[maybe_unused]] auto hSpaceCheck = [this](uintptr_t addr) -> bool { return !IsInYoung(addr); };
347 [[maybe_unused]] auto tFree =
348 reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
349 const size_t yObjSize = maxYSize / 10;
350 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
351 size_t initialHeap = ms->GetFootprintHeap();
352
353 {
354 // Ordinary young shall not be broken when intermixed with explicits
355 int i = 0;
356 size_t allocated = 0;
357 while (allocated < 2U * heapSize) {
358 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
359 ySpaceCheck);
360 allocated += bytes;
361 // NOLINTNEXTLINE(readability-magic-numbers)
362 if (i++ % 100_I == 0) {
363 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
364 }
365 }
366 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
367 ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
368 }
369
370 {
371 // Intensive allocations surviving 1 young
372 auto oldRootSize = rootSize;
373 size_t allocated = 0;
374 bool gcHappened = false;
375 GCHook gchook([&oldRootSize, this, &gcHappened]() {
376 MakeObjectsGarbage(oldRootSize, this->rootSize);
377 oldRootSize = this->rootSize;
378 gcHappened = true;
379 });
380 gc->AddListener(&gchook);
381 while (allocated < 4U * heapSize) {
382 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
383 ySpaceCheck);
384 MakeObjectsAlive(ov1, 1);
385 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
386 ->GetCurrentFreeTenuredSize();
387 allocated += bytes;
388 }
389 MakeObjectsGarbage(oldRootSize, rootSize);
390 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
391 ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
392 gc->RemoveListener(&gchook);
393 }
394
395 MakeObjectsGarbage(0, rootSize);
396 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
397 ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
398 }
399 ResetRuntime();
400 }
401
TEST_F(G1GCFullGCTest,TestExplicitFullNearLimit)402 TEST_F(G1GCFullGCTest, TestExplicitFullNearLimit)
403 {
404 SetupRuntime(static_cast<std::string>(GCStringFromType(GCType::G1_GC)));
405 {
406 HandleScope<ObjectHeader *> scope(thread);
407 PrepareTest<ark::PandaAssemblyLanguageConfig>();
408 [[maybe_unused]] size_t bytes;
409 [[maybe_unused]] size_t rawObjectsSize;
410
411 [[maybe_unused]] size_t youngSize =
412 reinterpret_cast<GenerationalSpaces *>(
413 reinterpret_cast<ObjectAllocatorGenBase *>(objectAllocator)->GetHeapSpace())
414 ->GetCurrentYoungSize();
415 [[maybe_unused]] size_t heapSize = mem::MemConfig::GetHeapSizeLimit();
416 [[maybe_unused]] auto g1Alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(objectAllocator);
417
418 [[maybe_unused]] auto ySpaceCheck = [this](uintptr_t addr) -> bool { return IsInYoung(addr); };
419 [[maybe_unused]] auto hSpaceCheck = [this](uintptr_t addr) -> bool { return !IsInYoung(addr); };
420 [[maybe_unused]] auto tFree =
421 reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
422 const size_t yObjSize = g1Alloc->GetYoungAllocMaxSize() / 10U;
423 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
424 size_t initialHeap = ms->GetFootprintHeap();
425
426 {
427 // Allocating until we are close to OOM, then do release this mem,
428 // do explicit full and allocate the same size once again
429 auto oldRootSize = rootSize;
430 int i = 0;
431 // NOLINTNEXTLINE(readability-magic-numbers)
432 while (tFree > 2.2F * youngSize) {
433 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
434 ySpaceCheck);
435 MakeObjectsAlive(ov1, 1);
436 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
437 ->GetCurrentFreeTenuredSize();
438 i++;
439 }
440 gc->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
441 MakeObjectsGarbage(oldRootSize, rootSize);
442
443 oldRootSize = rootSize;
444 while (--i > 0) {
445 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
446 ySpaceCheck);
447 MakeObjectsAlive(ov1, 1);
448 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
449 ->GetCurrentFreeTenuredSize();
450 }
451 MakeObjectsGarbage(oldRootSize, rootSize);
452 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
453 }
454
455 MakeObjectsGarbage(0, rootSize);
456 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
457 ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
458 }
459 ResetRuntime();
460 }
461
TEST_F(G1GCFullGCTest,TestOOMFullNearLimit)462 TEST_F(G1GCFullGCTest, TestOOMFullNearLimit)
463 {
464 SetupRuntime(static_cast<std::string>(GCStringFromType(GCType::G1_GC)));
465 {
466 HandleScope<ObjectHeader *> scope(thread);
467 PrepareTest<ark::PandaAssemblyLanguageConfig>();
468 [[maybe_unused]] size_t bytes;
469 [[maybe_unused]] size_t rawObjectsSize;
470
471 [[maybe_unused]] size_t youngSize =
472 reinterpret_cast<GenerationalSpaces *>(
473 reinterpret_cast<ObjectAllocatorGenBase *>(objectAllocator)->GetHeapSpace())
474 ->GetCurrentYoungSize();
475 [[maybe_unused]] size_t heapSize = mem::MemConfig::GetHeapSizeLimit();
476 [[maybe_unused]] auto g1Alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(objectAllocator);
477
478 [[maybe_unused]] auto ySpaceCheck = [this](uintptr_t addr) -> bool { return IsInYoung(addr); };
479 [[maybe_unused]] auto hSpaceCheck = [this](uintptr_t addr) -> bool { return !IsInYoung(addr); };
480 [[maybe_unused]] auto tFree =
481 reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
482 const size_t yObjSize = g1Alloc->GetYoungAllocMaxSize() / 10U;
483 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
484 size_t initialHeap = ms->GetFootprintHeap();
485
486 {
487 // Allocating until we are close to OOM, then do release this mem,
488 // then allocate the same size once again checking if we can handle it w/o OOM
489 auto oldRootSize = rootSize;
490 int i = 0;
491 // NOLINTNEXTLINE(readability-magic-numbers)
492 while (tFree > 2.2F * youngSize) {
493 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
494 ySpaceCheck);
495 MakeObjectsAlive(ov1, 1);
496 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
497 ->GetCurrentFreeTenuredSize();
498 i++;
499 }
500 MakeObjectsGarbage(oldRootSize, rootSize);
501
502 oldRootSize = rootSize;
503 while (--i > 0) {
504 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
505 ySpaceCheck);
506 MakeObjectsAlive(ov1, 1);
507 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
508 ->GetCurrentFreeTenuredSize();
509 }
510 MakeObjectsGarbage(oldRootSize, rootSize);
511 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
512 }
513
514 MakeObjectsGarbage(0, rootSize);
515 gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
516 ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
517 }
518 ResetRuntime();
519 }
520 } // namespace ark::mem::test
521