TaskTests.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/Task/TaskGraph.h>
  9. #include <AzCore/Task/TaskExecutor.h>
  10. #include <AzCore/Memory/PoolAllocator.h>
  11. #include <AzCore/UnitTest/TestTypes.h>
  12. #include <random>
  13. using AZ::TaskDescriptor;
  14. using AZ::TaskGraph;
  15. using AZ::TaskGraphEvent;
  16. using AZ::TaskExecutor;
  17. using AZ::Internal::Task;
  18. using AZ::TaskPriority;
  19. static TaskDescriptor defaultTD{ "TaskGraphTestTask", "TaskGraphTests" };
  20. namespace UnitTest
  21. {
  22. class TaskGraphTestFixture : public LeakDetectionFixture
  23. {
  24. public:
  25. void SetUp() override
  26. {
  27. LeakDetectionFixture::SetUp();
  28. m_executor = aznew TaskExecutor();
  29. TaskExecutor::SetInstance(m_executor); // SetInstance is a null-op if there is already a default instance set
  30. }
  31. void TearDown() override
  32. {
  33. if (&TaskExecutor::Instance() == m_executor) // if this test created the default instance unset it before destroying it
  34. {
  35. TaskExecutor::SetInstance(nullptr);
  36. }
  37. azdestroy(m_executor);
  38. LeakDetectionFixture::TearDown();
  39. }
  40. protected:
  41. TaskExecutor* m_executor;
  42. };
  43. TEST(TaskGraphTests, TrivialTaskLambda)
  44. {
  45. int x = 0;
  46. Task task(
  47. defaultTD,
  48. [&x]()
  49. {
  50. ++x;
  51. });
  52. task.Invoke();
  53. EXPECT_EQ(1, x);
  54. }
  55. TEST(TaskGraphTests, TrivialTaskLambdaMove)
  56. {
  57. int x = 0;
  58. Task task(
  59. defaultTD,
  60. [&x]()
  61. {
  62. ++x;
  63. });
  64. Task task2 = AZStd::move(task);
  65. task2.Invoke();
  66. EXPECT_EQ(1, x);
  67. }
  68. struct TrackMoves
  69. {
  70. TrackMoves() = default;
  71. TrackMoves(const TrackMoves&) = delete;
  72. TrackMoves(TrackMoves&& other)
  73. : moveCount{other.moveCount + 1}
  74. {
  75. }
  76. int moveCount = 0;
  77. };
  78. struct TrackCopies
  79. {
  80. TrackCopies() = default;
  81. TrackCopies(TrackCopies&&) = delete;
  82. TrackCopies(const TrackCopies& other)
  83. : copyCount{other.copyCount + 1}
  84. {
  85. }
  86. int copyCount = 0;
  87. };
  88. /*
  89. TEST(TaskGraphTests, ThisShouldNotCompile)
  90. {
  91. auto lambda = []
  92. {
  93. };
  94. Task task(defaultTD, lambda);
  95. task.Invoke();
  96. }
  97. */
  98. TEST(TaskGraphTests, MoveOnlyTaskLambda)
  99. {
  100. TrackMoves tm;
  101. int moveCount = 0;
  102. Task task(
  103. defaultTD,
  104. [tm = AZStd::move(tm), &moveCount]
  105. {
  106. moveCount = tm.moveCount;
  107. });
  108. task.Invoke();
  109. // Two moves are expected. Once into the capture body of the lambda, once to construct
  110. // the type erased task
  111. EXPECT_EQ(2, moveCount);
  112. }
  113. TEST(TaskGraphTests, MoveOnlyTaskLambdaMove)
  114. {
  115. TrackMoves tm;
  116. int moveCount = 0;
  117. Task task(
  118. defaultTD,
  119. [tm = AZStd::move(tm), &moveCount]
  120. {
  121. moveCount = tm.moveCount;
  122. });
  123. Task task2 = AZStd::move(task);
  124. task2.Invoke();
  125. EXPECT_EQ(3, moveCount);
  126. }
  127. TEST(TaskGraphTests, CopyOnlyTaskLambda)
  128. {
  129. TrackCopies tc;
  130. int copyCount = 0;
  131. Task task(
  132. defaultTD,
  133. [tc, &copyCount]
  134. {
  135. copyCount = tc.copyCount;
  136. });
  137. task.Invoke();
  138. // Two copies are expected. Once into the capture body of the lambda, once to construct
  139. // the type erased task
  140. EXPECT_EQ(2, copyCount);
  141. }
  142. TEST(TaskGraphTests, CopyOnlyTaskLambdaMove)
  143. {
  144. TrackCopies tc;
  145. int copyCount = 0;
  146. Task task(
  147. defaultTD,
  148. [tc, &copyCount]
  149. {
  150. copyCount = tc.copyCount;
  151. });
  152. Task task2 = AZStd::move(task);
  153. task2.Invoke();
  154. EXPECT_EQ(3, copyCount);
  155. }
  156. TEST(TaskGraphTests, DestroyLambda)
  157. {
  158. // This test ensures that for a lambda with a destructor, the destructor is invoked
  159. // exactly once on a non-moved-from object.
  160. int x = 0;
  161. struct TrackDestroy
  162. {
  163. TrackDestroy(int* px)
  164. : count{ px }
  165. {
  166. }
  167. TrackDestroy(TrackDestroy&& other)
  168. : count{ other.count }
  169. {
  170. other.count = nullptr;
  171. }
  172. ~TrackDestroy()
  173. {
  174. if (count)
  175. {
  176. ++*count;
  177. }
  178. }
  179. int* count = nullptr;
  180. };
  181. {
  182. TrackDestroy td{ &x };
  183. Task task(
  184. defaultTD,
  185. [td = AZStd::move(td)]
  186. {
  187. AZ_UNUSED(td);
  188. });
  189. task.Invoke();
  190. // Destructor should not have run yet (except on moved-from instances)
  191. EXPECT_EQ(x, 0);
  192. }
  193. // Destructor should have run now
  194. EXPECT_EQ(x, 1);
  195. }
  196. TEST_F(TaskGraphTestFixture, SingleTask)
  197. {
  198. AZStd::atomic_int32_t x = 0;
  199. TaskGraph graph{ "SingleTask" };
  200. graph.AddTask(
  201. defaultTD,
  202. [&x]
  203. {
  204. x = 1;
  205. });
  206. TaskGraphEvent ev{ "ev" };
  207. graph.SubmitOnExecutor(*m_executor, &ev);
  208. ev.Wait();
  209. EXPECT_EQ(1, x);
  210. }
  211. TEST_F(TaskGraphTestFixture, SingleTaskChain)
  212. {
  213. AZStd::atomic_int32_t x = 0;
  214. TaskGraph graph{ "SingleTaskChain" };
  215. auto a = graph.AddTask(
  216. defaultTD,
  217. [&x]
  218. {
  219. x += 1;
  220. });
  221. auto b = graph.AddTask(
  222. defaultTD,
  223. [&x]
  224. {
  225. x += 1;
  226. });
  227. b.Precedes(a);
  228. TaskGraphEvent ev{ "ev" };
  229. graph.SubmitOnExecutor(*m_executor, &ev);
  230. ev.Wait();
  231. EXPECT_EQ(2, x);
  232. }
  233. TEST_F(TaskGraphTestFixture, MultipleIndependentTaskChains)
  234. {
  235. AZStd::atomic_int32_t x = 0;
  236. constexpr int numChains = 5;
  237. TaskGraph graph{ "MultipleIndependentTaskChains" };
  238. for( int i = 0; i < numChains; ++i)
  239. {
  240. auto a = graph.AddTask(
  241. defaultTD,
  242. [&x]
  243. {
  244. x += 1;
  245. });
  246. auto b = graph.AddTask(
  247. defaultTD,
  248. [&x]
  249. {
  250. x += 1;
  251. });
  252. b.Precedes(a);
  253. }
  254. TaskGraphEvent ev{ "ev" };
  255. graph.SubmitOnExecutor(*m_executor, &ev);
  256. ev.Wait();
  257. EXPECT_EQ(2*numChains, x);
  258. }
  259. TEST_F(TaskGraphTestFixture, VariadicInterface)
  260. {
  261. int x = 0;
  262. TaskGraph graph{ "VariadicInterface" };
  263. auto [a, b, c] = graph.AddTasks(
  264. defaultTD,
  265. [&]
  266. {
  267. x += 3;
  268. },
  269. [&]
  270. {
  271. x = 4 * x;
  272. },
  273. [&]
  274. {
  275. x -= 1;
  276. });
  277. a.Precedes(b);
  278. b.Precedes(c);
  279. TaskGraphEvent ev{ "ev" };
  280. graph.SubmitOnExecutor(*m_executor, &ev);
  281. ev.Wait();
  282. EXPECT_EQ(11, x);
  283. }
  284. TEST_F(TaskGraphTestFixture, SerialGraph)
  285. {
  286. int x = 0;
  287. TaskGraph graph{ "SerialGraph" };
  288. auto a = graph.AddTask(
  289. defaultTD,
  290. [&]
  291. {
  292. x += 3;
  293. });
  294. auto b = graph.AddTask(
  295. defaultTD,
  296. [&]
  297. {
  298. x = 4 * x;
  299. });
  300. auto c = graph.AddTask(
  301. defaultTD,
  302. [&]
  303. {
  304. x -= 1;
  305. });
  306. a.Precedes(b);
  307. b.Precedes(c);
  308. TaskGraphEvent ev{ "ev" };
  309. graph.SubmitOnExecutor(*m_executor, &ev);
  310. ev.Wait();
  311. EXPECT_EQ(11, x);
  312. }
  313. TEST_F(TaskGraphTestFixture, DetachedGraph)
  314. {
  315. int x = 0;
  316. TaskGraphEvent ev{ "ev" };
  317. {
  318. TaskGraph graph{ "DetachedGraph" };
  319. auto a = graph.AddTask(
  320. defaultTD,
  321. [&]
  322. {
  323. x += 3;
  324. });
  325. auto b = graph.AddTask(
  326. defaultTD,
  327. [&]
  328. {
  329. x = 4 * x;
  330. });
  331. auto c = graph.AddTask(
  332. defaultTD,
  333. [&]
  334. {
  335. x -= 1;
  336. });
  337. a.Precedes(b);
  338. b.Precedes(c);
  339. graph.Detach();
  340. graph.SubmitOnExecutor(*m_executor, &ev);
  341. }
  342. ev.Wait();
  343. EXPECT_EQ(11, x);
  344. }
  345. TEST_F(TaskGraphTestFixture, ForkJoin)
  346. {
  347. AZStd::atomic<int> x = 0;
  348. // Task a initializes x to 3
  349. // Task b and c toggles the lowest two bits atomically
  350. // Task d decrements x
  351. TaskGraph graph{ "ForkJoin" };
  352. auto a = graph.AddTask(
  353. defaultTD,
  354. [&]
  355. {
  356. x = 0b111;
  357. });
  358. auto b = graph.AddTask(
  359. defaultTD,
  360. [&]
  361. {
  362. x ^= 1;
  363. });
  364. auto c = graph.AddTask(
  365. defaultTD,
  366. [&]
  367. {
  368. x ^= 2;
  369. });
  370. auto d = graph.AddTask(
  371. defaultTD,
  372. [&]
  373. {
  374. x -= 1;
  375. });
  376. /*
  377. a <-- Root
  378. / \
  379. b c
  380. \ /
  381. d
  382. */
  383. a.Precedes(b, c);
  384. d.Follows(b, c);
  385. TaskGraphEvent ev{ "ev" };
  386. graph.SubmitOnExecutor(*m_executor, &ev);
  387. ev.Wait();
  388. EXPECT_EQ(3, x);
  389. }
  390. // Waiting inside a task is disallowed , test that it fails correctly
  391. TEST_F(TaskGraphTestFixture, SpawnSubgraph)
  392. {
  393. AZStd::atomic<int> x = 0;
  394. TaskGraph graph{ "SpawnSubgraph" };
  395. auto a = graph.AddTask(
  396. defaultTD,
  397. [&]
  398. {
  399. x = 0b111;
  400. });
  401. auto b = graph.AddTask(
  402. defaultTD,
  403. [&]
  404. {
  405. x ^= 1;
  406. });
  407. auto c = graph.AddTask(
  408. defaultTD,
  409. [&]
  410. {
  411. x ^= 2;
  412. TaskGraph subgraph{ "InnerSubgraph" };
  413. auto e = subgraph.AddTask(
  414. defaultTD,
  415. [&]
  416. {
  417. x ^= 0b1000;
  418. });
  419. auto f = subgraph.AddTask(
  420. defaultTD,
  421. [&]
  422. {
  423. x ^= 0b10000;
  424. });
  425. auto g = subgraph.AddTask(
  426. defaultTD,
  427. [&]
  428. {
  429. x += 0b1000;
  430. });
  431. e.Precedes(g);
  432. f.Precedes(g);
  433. TaskGraphEvent ev{ "ev" };
  434. subgraph.SubmitOnExecutor(*m_executor, &ev);
  435. // TaskGraphEvent::Wait asserts if called on a worker thread, suppress & validate assert
  436. AZ_TEST_START_TRACE_SUPPRESSION;
  437. ev.Wait();
  438. AZ_TEST_STOP_TRACE_SUPPRESSION(1);
  439. });
  440. auto d = graph.AddTask(
  441. defaultTD,
  442. [&]
  443. {
  444. x -= 1;
  445. });
  446. /*
  447. NOTE: The ideal way to express this topology is without the wait on the subgraph
  448. at task g, but this is more an illustrative test. Better is to express the entire
  449. graph in a single larger graph.
  450. a <-- Root
  451. / \
  452. b c - f
  453. \ \ \
  454. \ e - g
  455. \ /
  456. \ /
  457. \ /
  458. d
  459. */
  460. a.Precedes(b);
  461. a.Precedes(c);
  462. b.Precedes(d);
  463. c.Precedes(d);
  464. TaskGraphEvent ev{ "ev" };
  465. graph.SubmitOnExecutor(*m_executor, &ev);
  466. ev.Wait();
  467. }
  468. TEST_F(TaskGraphTestFixture, RetainedGraph)
  469. {
  470. AZStd::atomic<int> x = 0;
  471. TaskGraph graph{ "RetainedGraph" };
  472. auto a = graph.AddTask(
  473. defaultTD,
  474. [&]
  475. {
  476. x = 0b111;
  477. });
  478. auto b = graph.AddTask(
  479. defaultTD,
  480. [&]
  481. {
  482. x ^= 1;
  483. });
  484. auto c = graph.AddTask(
  485. defaultTD,
  486. [&]
  487. {
  488. x ^= 2;
  489. });
  490. auto d = graph.AddTask(
  491. defaultTD,
  492. [&]
  493. {
  494. x -= 1;
  495. });
  496. auto e = graph.AddTask(
  497. defaultTD,
  498. [&]
  499. {
  500. x ^= 0b1000;
  501. });
  502. auto f = graph.AddTask(
  503. defaultTD,
  504. [&]
  505. {
  506. x ^= 0b10000;
  507. });
  508. auto g = graph.AddTask(
  509. defaultTD,
  510. [&]
  511. {
  512. x += 0b1000;
  513. });
  514. /*
  515. a <-- Root
  516. / \
  517. b c - f
  518. \ \ \
  519. \ e - g
  520. \ /
  521. \ /
  522. \ /
  523. d
  524. */
  525. a.Precedes(b, c);
  526. b.Precedes(d);
  527. c.Precedes(e, f);
  528. g.Follows(e, f);
  529. g.Precedes(d);
  530. TaskGraphEvent ev1{ "ev1" };
  531. graph.SubmitOnExecutor(*m_executor, &ev1);
  532. ev1.Wait();
  533. EXPECT_EQ(3 | 0b100000, x);
  534. x = 0;
  535. TaskGraphEvent ev2{ "ev2" };
  536. graph.SubmitOnExecutor(*m_executor, &ev2);
  537. ev2.Wait();
  538. EXPECT_EQ(3 | 0b100000, x);
  539. }
  540. } // namespace UnitTest
  541. #if defined(HAVE_BENCHMARK)
  542. namespace Benchmark
  543. {
  544. class TaskGraphBenchmarkFixture : public ::benchmark::Fixture
  545. {
  546. void internalSetUp()
  547. {
  548. executor = new TaskExecutor;
  549. TaskExecutor::SetInstance(executor);
  550. graph = new TaskGraph{ "BenchmarkFixture" };
  551. }
  552. void internalTearDown()
  553. {
  554. delete graph;
  555. delete executor;
  556. TaskExecutor::SetInstance(nullptr);
  557. }
  558. public:
  559. void SetUp(const benchmark::State&) override
  560. {
  561. internalSetUp();
  562. }
  563. void SetUp(benchmark::State&) override
  564. {
  565. internalSetUp();
  566. }
  567. void TearDown(const benchmark::State&) override
  568. {
  569. internalTearDown();
  570. }
  571. void TearDown(benchmark::State&) override
  572. {
  573. internalTearDown();
  574. }
  575. TaskDescriptor descriptors[4] = { { "critical", "benchmark", TaskPriority::CRITICAL },
  576. { "high", "benchmark", TaskPriority::HIGH },
  577. { "medium", "benchmark", TaskPriority::MEDIUM },
  578. { "low", "benchmark", TaskPriority::LOW } };
  579. TaskGraph* graph;
  580. TaskExecutor* executor;
  581. };
  582. BENCHMARK_F(TaskGraphBenchmarkFixture, QueueToDequeue)(benchmark::State& state)
  583. {
  584. graph->AddTask(
  585. descriptors[2],
  586. []
  587. {
  588. });
  589. for ([[maybe_unused]] auto _ : state)
  590. {
  591. TaskGraphEvent ev{ "ev" };
  592. graph->SubmitOnExecutor(*executor, &ev);
  593. ev.Wait();
  594. }
  595. }
  596. BENCHMARK_F(TaskGraphBenchmarkFixture, OneAfterAnother)(benchmark::State& state)
  597. {
  598. auto a = graph->AddTask(
  599. descriptors[2],
  600. []
  601. {
  602. });
  603. auto b = graph->AddTask(
  604. descriptors[2],
  605. []
  606. {
  607. });
  608. a.Precedes(b);
  609. for ([[maybe_unused]] auto _ : state)
  610. {
  611. TaskGraphEvent ev{ "ev" };
  612. graph->SubmitOnExecutor(*executor, &ev);
  613. ev.Wait();
  614. }
  615. }
  616. BENCHMARK_F(TaskGraphBenchmarkFixture, FourToOneJoin)(benchmark::State& state)
  617. {
  618. auto [a, b, c, d, e] = graph->AddTasks(
  619. descriptors[2],
  620. []
  621. {
  622. },
  623. []
  624. {
  625. },
  626. []
  627. {
  628. },
  629. []
  630. {
  631. },
  632. []
  633. {
  634. });
  635. e.Follows(a, b, c, d);
  636. for ([[maybe_unused]] auto _ : state)
  637. {
  638. TaskGraphEvent ev{ "ev" };
  639. graph->SubmitOnExecutor(*executor, &ev);
  640. ev.Wait();
  641. }
  642. }
  643. } // namespace Benchmark
  644. #endif