123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <AzCore/Task/TaskGraph.h>
- #include <AzCore/Task/TaskExecutor.h>
- #include <AzCore/Memory/PoolAllocator.h>
- #include <AzCore/UnitTest/TestTypes.h>
- #include <random>
- using AZ::TaskDescriptor;
- using AZ::TaskGraph;
- using AZ::TaskGraphEvent;
- using AZ::TaskExecutor;
- using AZ::Internal::Task;
- using AZ::TaskPriority;
- static TaskDescriptor defaultTD{ "TaskGraphTestTask", "TaskGraphTests" };
- namespace UnitTest
- {
- class TaskGraphTestFixture : public LeakDetectionFixture
- {
- public:
- void SetUp() override
- {
- LeakDetectionFixture::SetUp();
- m_executor = aznew TaskExecutor();
- TaskExecutor::SetInstance(m_executor); // SetInstance is a null-op if there is already a default instance set
- }
- void TearDown() override
- {
- if (&TaskExecutor::Instance() == m_executor) // if this test created the default instance unset it before destroying it
- {
- TaskExecutor::SetInstance(nullptr);
- }
- azdestroy(m_executor);
- LeakDetectionFixture::TearDown();
- }
- protected:
- TaskExecutor* m_executor;
- };
- TEST(TaskGraphTests, TrivialTaskLambda)
- {
- int x = 0;
- Task task(
- defaultTD,
- [&x]()
- {
- ++x;
- });
- task.Invoke();
- EXPECT_EQ(1, x);
- }
- TEST(TaskGraphTests, TrivialTaskLambdaMove)
- {
- int x = 0;
- Task task(
- defaultTD,
- [&x]()
- {
- ++x;
- });
- Task task2 = AZStd::move(task);
- task2.Invoke();
- EXPECT_EQ(1, x);
- }
- struct TrackMoves
- {
- TrackMoves() = default;
- TrackMoves(const TrackMoves&) = delete;
- TrackMoves(TrackMoves&& other)
- : moveCount{other.moveCount + 1}
- {
- }
- int moveCount = 0;
- };
- struct TrackCopies
- {
- TrackCopies() = default;
- TrackCopies(TrackCopies&&) = delete;
- TrackCopies(const TrackCopies& other)
- : copyCount{other.copyCount + 1}
- {
- }
- int copyCount = 0;
- };
- /*
- TEST(TaskGraphTests, ThisShouldNotCompile)
- {
- auto lambda = []
- {
- };
- Task task(defaultTD, lambda);
- task.Invoke();
- }
- */
- TEST(TaskGraphTests, MoveOnlyTaskLambda)
- {
- TrackMoves tm;
- int moveCount = 0;
- Task task(
- defaultTD,
- [tm = AZStd::move(tm), &moveCount]
- {
- moveCount = tm.moveCount;
- });
- task.Invoke();
- // Two moves are expected. Once into the capture body of the lambda, once to construct
- // the type erased task
- EXPECT_EQ(2, moveCount);
- }
- TEST(TaskGraphTests, MoveOnlyTaskLambdaMove)
- {
- TrackMoves tm;
- int moveCount = 0;
- Task task(
- defaultTD,
- [tm = AZStd::move(tm), &moveCount]
- {
- moveCount = tm.moveCount;
- });
- Task task2 = AZStd::move(task);
- task2.Invoke();
- EXPECT_EQ(3, moveCount);
- }
- TEST(TaskGraphTests, CopyOnlyTaskLambda)
- {
- TrackCopies tc;
- int copyCount = 0;
- Task task(
- defaultTD,
- [tc, ©Count]
- {
- copyCount = tc.copyCount;
- });
- task.Invoke();
- // Two copies are expected. Once into the capture body of the lambda, once to construct
- // the type erased task
- EXPECT_EQ(2, copyCount);
- }
- TEST(TaskGraphTests, CopyOnlyTaskLambdaMove)
- {
- TrackCopies tc;
- int copyCount = 0;
- Task task(
- defaultTD,
- [tc, ©Count]
- {
- copyCount = tc.copyCount;
- });
- Task task2 = AZStd::move(task);
- task2.Invoke();
- EXPECT_EQ(3, copyCount);
- }
- TEST(TaskGraphTests, DestroyLambda)
- {
- // This test ensures that for a lambda with a destructor, the destructor is invoked
- // exactly once on a non-moved-from object.
- int x = 0;
- struct TrackDestroy
- {
- TrackDestroy(int* px)
- : count{ px }
- {
- }
- TrackDestroy(TrackDestroy&& other)
- : count{ other.count }
- {
- other.count = nullptr;
- }
- ~TrackDestroy()
- {
- if (count)
- {
- ++*count;
- }
- }
- int* count = nullptr;
- };
- {
- TrackDestroy td{ &x };
- Task task(
- defaultTD,
- [td = AZStd::move(td)]
- {
- AZ_UNUSED(td);
- });
- task.Invoke();
- // Destructor should not have run yet (except on moved-from instances)
- EXPECT_EQ(x, 0);
- }
- // Destructor should have run now
- EXPECT_EQ(x, 1);
- }
- TEST_F(TaskGraphTestFixture, SingleTask)
- {
- AZStd::atomic_int32_t x = 0;
- TaskGraph graph{ "SingleTask" };
- graph.AddTask(
- defaultTD,
- [&x]
- {
- x = 1;
- });
- TaskGraphEvent ev{ "ev" };
- graph.SubmitOnExecutor(*m_executor, &ev);
- ev.Wait();
- EXPECT_EQ(1, x);
- }
- TEST_F(TaskGraphTestFixture, SingleTaskChain)
- {
- AZStd::atomic_int32_t x = 0;
- TaskGraph graph{ "SingleTaskChain" };
- auto a = graph.AddTask(
- defaultTD,
- [&x]
- {
- x += 1;
- });
- auto b = graph.AddTask(
- defaultTD,
- [&x]
- {
- x += 1;
- });
- b.Precedes(a);
- TaskGraphEvent ev{ "ev" };
- graph.SubmitOnExecutor(*m_executor, &ev);
- ev.Wait();
- EXPECT_EQ(2, x);
- }
- TEST_F(TaskGraphTestFixture, MultipleIndependentTaskChains)
- {
- AZStd::atomic_int32_t x = 0;
- constexpr int numChains = 5;
- TaskGraph graph{ "MultipleIndependentTaskChains" };
- for( int i = 0; i < numChains; ++i)
- {
- auto a = graph.AddTask(
- defaultTD,
- [&x]
- {
- x += 1;
- });
- auto b = graph.AddTask(
- defaultTD,
- [&x]
- {
- x += 1;
- });
- b.Precedes(a);
- }
- TaskGraphEvent ev{ "ev" };
- graph.SubmitOnExecutor(*m_executor, &ev);
- ev.Wait();
- EXPECT_EQ(2*numChains, x);
- }
- TEST_F(TaskGraphTestFixture, VariadicInterface)
- {
- int x = 0;
- TaskGraph graph{ "VariadicInterface" };
- auto [a, b, c] = graph.AddTasks(
- defaultTD,
- [&]
- {
- x += 3;
- },
- [&]
- {
- x = 4 * x;
- },
- [&]
- {
- x -= 1;
- });
- a.Precedes(b);
- b.Precedes(c);
- TaskGraphEvent ev{ "ev" };
- graph.SubmitOnExecutor(*m_executor, &ev);
- ev.Wait();
- EXPECT_EQ(11, x);
- }
- TEST_F(TaskGraphTestFixture, SerialGraph)
- {
- int x = 0;
- TaskGraph graph{ "SerialGraph" };
- auto a = graph.AddTask(
- defaultTD,
- [&]
- {
- x += 3;
- });
- auto b = graph.AddTask(
- defaultTD,
- [&]
- {
- x = 4 * x;
- });
- auto c = graph.AddTask(
- defaultTD,
- [&]
- {
- x -= 1;
- });
- a.Precedes(b);
- b.Precedes(c);
- TaskGraphEvent ev{ "ev" };
- graph.SubmitOnExecutor(*m_executor, &ev);
- ev.Wait();
- EXPECT_EQ(11, x);
- }
- TEST_F(TaskGraphTestFixture, DetachedGraph)
- {
- int x = 0;
- TaskGraphEvent ev{ "ev" };
- {
- TaskGraph graph{ "DetachedGraph" };
- auto a = graph.AddTask(
- defaultTD,
- [&]
- {
- x += 3;
- });
- auto b = graph.AddTask(
- defaultTD,
- [&]
- {
- x = 4 * x;
- });
- auto c = graph.AddTask(
- defaultTD,
- [&]
- {
- x -= 1;
- });
- a.Precedes(b);
- b.Precedes(c);
- graph.Detach();
- graph.SubmitOnExecutor(*m_executor, &ev);
- }
- ev.Wait();
- EXPECT_EQ(11, x);
- }
- TEST_F(TaskGraphTestFixture, ForkJoin)
- {
- AZStd::atomic<int> x = 0;
- // Task a initializes x to 3
- // Task b and c toggles the lowest two bits atomically
- // Task d decrements x
- TaskGraph graph{ "ForkJoin" };
- auto a = graph.AddTask(
- defaultTD,
- [&]
- {
- x = 0b111;
- });
- auto b = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 1;
- });
- auto c = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 2;
- });
- auto d = graph.AddTask(
- defaultTD,
- [&]
- {
- x -= 1;
- });
- /*
- a <-- Root
- / \
- b c
- \ /
- d
- */
- a.Precedes(b, c);
- d.Follows(b, c);
- TaskGraphEvent ev{ "ev" };
- graph.SubmitOnExecutor(*m_executor, &ev);
- ev.Wait();
- EXPECT_EQ(3, x);
- }
- // Waiting inside a task is disallowed , test that it fails correctly
- TEST_F(TaskGraphTestFixture, SpawnSubgraph)
- {
- AZStd::atomic<int> x = 0;
- TaskGraph graph{ "SpawnSubgraph" };
- auto a = graph.AddTask(
- defaultTD,
- [&]
- {
- x = 0b111;
- });
- auto b = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 1;
- });
- auto c = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 2;
- TaskGraph subgraph{ "InnerSubgraph" };
- auto e = subgraph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 0b1000;
- });
- auto f = subgraph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 0b10000;
- });
- auto g = subgraph.AddTask(
- defaultTD,
- [&]
- {
- x += 0b1000;
- });
- e.Precedes(g);
- f.Precedes(g);
- TaskGraphEvent ev{ "ev" };
- subgraph.SubmitOnExecutor(*m_executor, &ev);
- // TaskGraphEvent::Wait asserts if called on a worker thread, suppress & validate assert
- AZ_TEST_START_TRACE_SUPPRESSION;
- ev.Wait();
- AZ_TEST_STOP_TRACE_SUPPRESSION(1);
- });
- auto d = graph.AddTask(
- defaultTD,
- [&]
- {
- x -= 1;
- });
- /*
- NOTE: The ideal way to express this topology is without the wait on the subgraph
- at task g, but this is more an illustrative test. Better is to express the entire
- graph in a single larger graph.
- a <-- Root
- / \
- b c - f
- \ \ \
- \ e - g
- \ /
- \ /
- \ /
- d
- */
- a.Precedes(b);
- a.Precedes(c);
- b.Precedes(d);
- c.Precedes(d);
- TaskGraphEvent ev{ "ev" };
- graph.SubmitOnExecutor(*m_executor, &ev);
- ev.Wait();
- }
- TEST_F(TaskGraphTestFixture, RetainedGraph)
- {
- AZStd::atomic<int> x = 0;
- TaskGraph graph{ "RetainedGraph" };
- auto a = graph.AddTask(
- defaultTD,
- [&]
- {
- x = 0b111;
- });
- auto b = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 1;
- });
- auto c = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 2;
- });
- auto d = graph.AddTask(
- defaultTD,
- [&]
- {
- x -= 1;
- });
- auto e = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 0b1000;
- });
- auto f = graph.AddTask(
- defaultTD,
- [&]
- {
- x ^= 0b10000;
- });
- auto g = graph.AddTask(
- defaultTD,
- [&]
- {
- x += 0b1000;
- });
- /*
- a <-- Root
- / \
- b c - f
- \ \ \
- \ e - g
- \ /
- \ /
- \ /
- d
- */
- a.Precedes(b, c);
- b.Precedes(d);
- c.Precedes(e, f);
- g.Follows(e, f);
- g.Precedes(d);
- TaskGraphEvent ev1{ "ev1" };
- graph.SubmitOnExecutor(*m_executor, &ev1);
- ev1.Wait();
- EXPECT_EQ(3 | 0b100000, x);
- x = 0;
- TaskGraphEvent ev2{ "ev2" };
- graph.SubmitOnExecutor(*m_executor, &ev2);
- ev2.Wait();
- EXPECT_EQ(3 | 0b100000, x);
- }
- } // namespace UnitTest
- #if defined(HAVE_BENCHMARK)
- namespace Benchmark
- {
- class TaskGraphBenchmarkFixture : public ::benchmark::Fixture
- {
- void internalSetUp()
- {
- executor = new TaskExecutor;
- TaskExecutor::SetInstance(executor);
- graph = new TaskGraph{ "BenchmarkFixture" };
- }
- void internalTearDown()
- {
- delete graph;
- delete executor;
- TaskExecutor::SetInstance(nullptr);
- }
- public:
- void SetUp(const benchmark::State&) override
- {
- internalSetUp();
- }
- void SetUp(benchmark::State&) override
- {
- internalSetUp();
- }
- void TearDown(const benchmark::State&) override
- {
- internalTearDown();
- }
- void TearDown(benchmark::State&) override
- {
- internalTearDown();
- }
- TaskDescriptor descriptors[4] = { { "critical", "benchmark", TaskPriority::CRITICAL },
- { "high", "benchmark", TaskPriority::HIGH },
- { "medium", "benchmark", TaskPriority::MEDIUM },
- { "low", "benchmark", TaskPriority::LOW } };
- TaskGraph* graph;
- TaskExecutor* executor;
- };
- BENCHMARK_F(TaskGraphBenchmarkFixture, QueueToDequeue)(benchmark::State& state)
- {
- graph->AddTask(
- descriptors[2],
- []
- {
- });
- for ([[maybe_unused]] auto _ : state)
- {
- TaskGraphEvent ev{ "ev" };
- graph->SubmitOnExecutor(*executor, &ev);
- ev.Wait();
- }
- }
- BENCHMARK_F(TaskGraphBenchmarkFixture, OneAfterAnother)(benchmark::State& state)
- {
- auto a = graph->AddTask(
- descriptors[2],
- []
- {
- });
- auto b = graph->AddTask(
- descriptors[2],
- []
- {
- });
- a.Precedes(b);
- for ([[maybe_unused]] auto _ : state)
- {
- TaskGraphEvent ev{ "ev" };
- graph->SubmitOnExecutor(*executor, &ev);
- ev.Wait();
- }
- }
- BENCHMARK_F(TaskGraphBenchmarkFixture, FourToOneJoin)(benchmark::State& state)
- {
- auto [a, b, c, d, e] = graph->AddTasks(
- descriptors[2],
- []
- {
- },
- []
- {
- },
- []
- {
- },
- []
- {
- },
- []
- {
- });
- e.Follows(a, b, c, d);
- for ([[maybe_unused]] auto _ : state)
- {
- TaskGraphEvent ev{ "ev" };
- graph->SubmitOnExecutor(*executor, &ev);
- ev.Wait();
- }
- }
- } // namespace Benchmark
- #endif
|