Add JobGraph::Reset, streamline execution, address feedback
Also, came up with more useful benchmarks that actually measure the enqueue/dequeue operations for various simple workflows. For retained graphs, time-of-flight from submission to execution is ~1us per job, indicating job granularity should be >20us for retained jobs. For dynamic jobs, where we need to pay the cost of allocation, a granularity of ~100+ us may be advised. Signed-off-by: Jeremy Ong <jcong@amazon.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Jobs/JobDescriptor.h>
|
||||
#include <AzCore/std/containers/fixed_vector.h>
|
||||
#include <AzCore/std/typetraits/is_assignable.h>
|
||||
#include <AzCore/std/typetraits/is_destructible.h>
|
||||
#include <AzCore/std/parallel/atomic.h>
|
||||
@@ -105,15 +106,16 @@ namespace AZ::Internal
|
||||
class alignas(alignof(max_align_t)) TypeErasedJob final
|
||||
{
|
||||
public:
|
||||
// The inline buffer allows the TypeErasedJob to span two cache lines. Lambdas can capture 56
|
||||
// bytes of data (7 pointers/references on a 64-bit machine) before spilling to the heap.
|
||||
constexpr static size_t BufferSize = 128 - sizeof(size_t) * 6 - sizeof(uint32_t) - sizeof(JobDescriptor);
|
||||
// The inline buffer allows the TypeErasedJob to span two cache lines. Lambdas can capture 48
|
||||
// bytes of data (6 pointers/references on a 64-bit machine) before spilling to the heap.
|
||||
constexpr static size_t BufferSize =
|
||||
128 - sizeof(size_t) * 6 - sizeof(uint32_t) - sizeof(JobDescriptor) - sizeof(AZStd::atomic<uint32_t>);
|
||||
|
||||
TypeErasedJob() = default;
|
||||
|
||||
template <typename Lambda>
|
||||
template<typename Lambda>
|
||||
TypeErasedJob(JobDescriptor const& desc, Lambda&& lambda) noexcept
|
||||
: m_descriptor{desc}
|
||||
: m_descriptor{ desc }
|
||||
{
|
||||
JobTypeEraser<Lambda> eraser;
|
||||
m_invoker = eraser.ErasedInvoker();
|
||||
@@ -147,9 +149,9 @@ namespace AZ::Internal
|
||||
// Indicates if this job is a root of the graph (with no dependencies)
|
||||
bool IsRoot();
|
||||
|
||||
void AttachToJobGraph(CompiledJobGraph& graph) noexcept
|
||||
void Init() noexcept
|
||||
{
|
||||
m_graph = &graph;
|
||||
m_dependencyCount = m_inboundLinkCount;
|
||||
}
|
||||
|
||||
void Invoke()
|
||||
@@ -167,7 +169,7 @@ namespace AZ::Internal
|
||||
friend class JobWorker;
|
||||
|
||||
// This relocation avoids branches needed if the lambda type is unknown
|
||||
template <typename Lambda>
|
||||
template<typename Lambda>
|
||||
void TypedRelocate(Lambda&& lambda, char* destination)
|
||||
{
|
||||
if constexpr (AZStd::is_trivially_move_constructible_v<Lambda>)
|
||||
@@ -195,6 +197,7 @@ namespace AZ::Internal
|
||||
// class to equal the alignment of the largest scalar type available on the system (generally
|
||||
// 16 bytes).
|
||||
char m_buffer[BufferSize];
|
||||
AZStd::atomic<uint32_t> m_dependencyCount;
|
||||
|
||||
// This value is an offset in a buffer that stores dependency tracking information.
|
||||
uint32_t m_successorOffset = 0;
|
||||
|
||||
@@ -29,19 +29,18 @@ namespace AZ
|
||||
AZStd::vector<TypeErasedJob>&& jobs,
|
||||
AZStd::unordered_map<uint32_t, AZStd::vector<uint32_t>>& links,
|
||||
size_t linkCount,
|
||||
bool retained)
|
||||
: m_remaining{ jobs.size() }
|
||||
, m_retained{ retained }
|
||||
JobGraph* parent)
|
||||
: m_parent{ parent }
|
||||
{
|
||||
m_jobs = AZStd::move(jobs);
|
||||
m_dependencyCounts = reinterpret_cast<AZStd::atomic<uint32_t>*>(azcalloc(sizeof(AZStd::atomic<uint32_t>) * m_jobs.size()));
|
||||
m_successors.resize(linkCount);
|
||||
|
||||
uint32_t* cursor = m_successors.data();
|
||||
TypeErasedJob** cursor = m_successors.data();
|
||||
|
||||
for (size_t i = 0; i != m_jobs.size(); ++i)
|
||||
{
|
||||
TypeErasedJob& job = m_jobs[i];
|
||||
job.m_graph = this;
|
||||
job.m_successorOffset = cursor - m_successors.data();
|
||||
cursor += job.m_outboundLinkCount;
|
||||
|
||||
@@ -49,54 +48,42 @@ namespace AZ
|
||||
|
||||
for (uint32_t j = 0; j != job.m_outboundLinkCount; ++j)
|
||||
{
|
||||
m_successors[static_cast<size_t>(job.m_successorOffset) + j] = links[i][j];
|
||||
}
|
||||
|
||||
if (job.m_inboundLinkCount > 0)
|
||||
{
|
||||
m_dependencyCounts[i].store(job.m_inboundLinkCount, AZStd::memory_order_release);
|
||||
m_successors[static_cast<size_t>(job.m_successorOffset) + j] = &m_jobs[links[i][j]];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check for dependency cycles
|
||||
}
|
||||
|
||||
CompiledJobGraph::~CompiledJobGraph()
|
||||
uint32_t CompiledJobGraph::Release()
|
||||
{
|
||||
if (m_dependencyCounts)
|
||||
{
|
||||
azfree(m_dependencyCounts);
|
||||
}
|
||||
}
|
||||
uint32_t remaining = --m_remaining;
|
||||
|
||||
void CompiledJobGraph::Release()
|
||||
{
|
||||
if (--m_remaining == 0)
|
||||
if (m_parent)
|
||||
{
|
||||
if (m_retained)
|
||||
if (remaining == 1)
|
||||
{
|
||||
m_remaining = m_jobs.size();
|
||||
for (size_t i = 0; i != m_jobs.size(); ++i)
|
||||
{
|
||||
TypeErasedJob& job = m_jobs[i];
|
||||
if (job.m_inboundLinkCount > 0)
|
||||
{
|
||||
m_dependencyCounts[i].store(job.m_inboundLinkCount, AZStd::memory_order_release);
|
||||
}
|
||||
}
|
||||
// Allow the parent graph to be submitted again
|
||||
m_parent->m_submitted = false;
|
||||
}
|
||||
|
||||
}
|
||||
else if (remaining == 0)
|
||||
{
|
||||
if (m_waitEvent)
|
||||
{
|
||||
m_waitEvent->m_submitted = false;
|
||||
m_waitEvent->Signal();
|
||||
}
|
||||
|
||||
if (!m_retained)
|
||||
{
|
||||
azdestroy(this);
|
||||
}
|
||||
azdestroy(this);
|
||||
return remaining;
|
||||
}
|
||||
|
||||
if (m_waitEvent && remaining == (m_parent ? 1 : 0))
|
||||
{
|
||||
m_waitEvent->Signal();
|
||||
}
|
||||
|
||||
return remaining;
|
||||
}
|
||||
|
||||
struct QueueStatus
|
||||
@@ -124,7 +111,7 @@ namespace AZ
|
||||
JobQueue(const JobQueue&) = delete;
|
||||
JobQueue& operator=(const JobQueue&) = delete;
|
||||
|
||||
bool Enqueue(TypeErasedJob* job);
|
||||
void Enqueue(TypeErasedJob* job);
|
||||
TypeErasedJob* TryDequeue();
|
||||
|
||||
private:
|
||||
@@ -132,7 +119,7 @@ namespace AZ
|
||||
TypeErasedJob* m_queues[PriorityLevelCount][MaxQueueSize] = {};
|
||||
};
|
||||
|
||||
bool JobQueue::Enqueue(TypeErasedJob* job)
|
||||
void JobQueue::Enqueue(TypeErasedJob* job)
|
||||
{
|
||||
uint8_t priority = job->GetPriorityNumber();
|
||||
QueueStatus& status = m_status[priority];
|
||||
@@ -159,7 +146,7 @@ namespace AZ
|
||||
expectedReserve = reserve;
|
||||
}
|
||||
|
||||
return status.head == status.tail - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// We failed to reserve a slot, try again
|
||||
@@ -233,9 +220,11 @@ namespace AZ
|
||||
|
||||
void Enqueue(TypeErasedJob* job)
|
||||
{
|
||||
if (m_queue.Enqueue(job))
|
||||
m_queue.Enqueue(job);
|
||||
|
||||
if (!m_busy.exchange(true))
|
||||
{
|
||||
// The queue was empty prior to enqueueing the job, release the semaphore
|
||||
// The worker was idle prior to enqueueing the job, release the semaphore
|
||||
m_semaphore.release();
|
||||
}
|
||||
}
|
||||
@@ -245,14 +234,16 @@ namespace AZ
|
||||
{
|
||||
while (m_active)
|
||||
{
|
||||
m_busy = false;
|
||||
m_semaphore.acquire();
|
||||
// m_semaphore.try_acquire_for(AZStd::chrono::microseconds{ 10 });
|
||||
|
||||
if (!m_active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_busy = true;
|
||||
|
||||
TypeErasedJob* job = m_queue.TryDequeue();
|
||||
while (job)
|
||||
{
|
||||
@@ -260,10 +251,10 @@ namespace AZ
|
||||
// Decrement counts for all job successors
|
||||
for (size_t j = 0; j != job->m_outboundLinkCount; ++j)
|
||||
{
|
||||
uint32_t successorIndex = job->m_graph->m_successors[job->m_successorOffset + j];
|
||||
if (--job->m_graph->m_dependencyCounts[successorIndex] == 0)
|
||||
TypeErasedJob* successor = job->m_graph->m_successors[job->m_successorOffset + j];
|
||||
if (--successor->m_dependencyCount == 0)
|
||||
{
|
||||
m_executor->Submit(job->m_graph->m_jobs[successorIndex]);
|
||||
m_executor->Submit(*successor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +268,7 @@ namespace AZ
|
||||
|
||||
AZStd::thread m_thread;
|
||||
AZStd::atomic<bool> m_active;
|
||||
AZStd::atomic<bool> m_busy;
|
||||
AZStd::binary_semaphore m_semaphore;
|
||||
|
||||
::AZ::JobExecutor* m_executor;
|
||||
@@ -327,11 +319,6 @@ namespace AZ
|
||||
|
||||
void JobExecutor::Submit(Internal::CompiledJobGraph& graph)
|
||||
{
|
||||
for (Internal::TypeErasedJob& job : graph.Jobs())
|
||||
{
|
||||
job.AttachToJobGraph(graph);
|
||||
}
|
||||
|
||||
// Submit all jobs that have no inbound edges
|
||||
for (Internal::TypeErasedJob& job : graph.Jobs())
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
namespace AZ
|
||||
{
|
||||
class JobGraphEvent;
|
||||
class JobGraph;
|
||||
|
||||
namespace Internal
|
||||
{
|
||||
@@ -30,9 +31,7 @@ namespace AZ
|
||||
AZStd::vector<TypeErasedJob>&& jobs,
|
||||
AZStd::unordered_map<uint32_t, AZStd::vector<uint32_t>>& links,
|
||||
size_t linkCount,
|
||||
bool retained);
|
||||
|
||||
~CompiledJobGraph();
|
||||
JobGraph* parent);
|
||||
|
||||
AZStd::vector<TypeErasedJob>& Jobs() noexcept
|
||||
{
|
||||
@@ -40,19 +39,19 @@ namespace AZ
|
||||
}
|
||||
|
||||
// Indicate that a constituent job has finished and decrement a counter to determine if the
|
||||
// graph should be freed
|
||||
void Release();
|
||||
// graph should be freed (returns the value after atomic decrement)
|
||||
uint32_t Release();
|
||||
|
||||
private:
|
||||
friend class JobGraph;
|
||||
friend class JobWorker;
|
||||
|
||||
AZStd::vector<TypeErasedJob> m_jobs;
|
||||
AZStd::vector<uint32_t> m_successors;
|
||||
AZStd::atomic<uint32_t>* m_dependencyCounts = nullptr;
|
||||
AZStd::vector<TypeErasedJob*> m_successors;
|
||||
JobGraphEvent* m_waitEvent = nullptr;
|
||||
// The pointer to the parent graph is set only if it is retained
|
||||
JobGraph* m_parent = nullptr;
|
||||
AZStd::atomic<uint32_t> m_remaining;
|
||||
bool m_retained;
|
||||
};
|
||||
|
||||
class JobWorker;
|
||||
|
||||
@@ -30,10 +30,27 @@ namespace AZ
|
||||
{
|
||||
if (m_retained && m_compiledJobGraph)
|
||||
{
|
||||
azdestroy(m_compiledJobGraph);
|
||||
// This job graph has already finished and we are potentially responsible for its destruction
|
||||
if (m_compiledJobGraph->Release() == 0)
|
||||
{
|
||||
azdestroy(m_compiledJobGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JobGraph::Reset()
|
||||
{
|
||||
AZ_Assert(!m_submitted, "Cannot reset a job graph while it is in flight");
|
||||
if (m_compiledJobGraph)
|
||||
{
|
||||
azdestroy(m_compiledJobGraph);
|
||||
m_compiledJobGraph = nullptr;
|
||||
}
|
||||
m_jobs.clear();
|
||||
m_links.clear();
|
||||
m_linkCount = 0;
|
||||
}
|
||||
|
||||
void JobGraph::Submit(JobGraphEvent* waitEvent)
|
||||
{
|
||||
SubmitOnExecutor(JobExecutor::Instance(), waitEvent);
|
||||
@@ -41,20 +58,28 @@ namespace AZ
|
||||
|
||||
void JobGraph::SubmitOnExecutor(JobExecutor& executor, JobGraphEvent* waitEvent)
|
||||
{
|
||||
m_submitted = true;
|
||||
|
||||
if (!m_compiledJobGraph)
|
||||
{
|
||||
m_compiledJobGraph = aznew CompiledJobGraph(AZStd::move(m_jobs), m_links, m_linkCount, m_retained);
|
||||
m_compiledJobGraph = aznew CompiledJobGraph(AZStd::move(m_jobs), m_links, m_linkCount, m_retained ? this : nullptr);
|
||||
}
|
||||
|
||||
m_compiledJobGraph->m_waitEvent = waitEvent;
|
||||
m_compiledJobGraph->m_remaining = m_compiledJobGraph->m_jobs.size() + (m_retained ? 1 : 0);
|
||||
for (size_t i = 0; i != m_compiledJobGraph->m_jobs.size(); ++i)
|
||||
{
|
||||
m_compiledJobGraph->m_jobs[i].Init();
|
||||
}
|
||||
|
||||
executor.Submit(*m_compiledJobGraph);
|
||||
|
||||
if (waitEvent)
|
||||
if (m_retained)
|
||||
{
|
||||
waitEvent->m_submitted = true;
|
||||
m_submitted = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_compiledJobGraph = nullptr;
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// suited in the private CompiledJobGraph implementation instead to keep this header lean.
|
||||
#include <AzCore/Jobs/Internal/JobTypeEraser.h>
|
||||
#include <AzCore/Jobs/JobDescriptor.h>
|
||||
#include <AzCore/std/containers/fixed_vector.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#include <AzCore/std/containers/unordered_map.h>
|
||||
#include <AzCore/std/parallel/binary_semaphore.h>
|
||||
@@ -30,10 +30,14 @@ namespace AZ
|
||||
class JobToken final
|
||||
{
|
||||
public:
|
||||
// Indicate that this job must finish before the job passed as the argument
|
||||
// Indicate that this job must finish before the job token(s) passed as the argument
|
||||
template <typename... JT>
|
||||
void Precedes(JT&... tokens);
|
||||
|
||||
// Indicate that this job must finish after the job token(s) passed as the argument
|
||||
template <typename... JT>
|
||||
void Succeeds(JT&... tokens);
|
||||
|
||||
private:
|
||||
friend class JobGraph;
|
||||
|
||||
@@ -67,7 +71,6 @@ namespace AZ
|
||||
void Signal();
|
||||
|
||||
AZStd::binary_semaphore m_semaphore;
|
||||
bool m_submitted = false;
|
||||
};
|
||||
|
||||
// The JobGraph encapsulates a set of jobs and their interdependencies. After adding
|
||||
@@ -81,13 +84,18 @@ namespace AZ
|
||||
public:
|
||||
~JobGraph();
|
||||
|
||||
// Reset the state of the job graph to begin recording jobs and edges again
|
||||
// NOTE: Graph must be in a "settled" state (cannot be in-flight)
|
||||
void Reset();
|
||||
|
||||
// Add a job to the graph, retrieiving a token that can be used to express dependencies
|
||||
// between jobs. The first argument specifies the JobKind, used for tracking the job.
|
||||
// NOTE: This operation is invalid if the graph is in-flight
|
||||
template<typename Lambda>
|
||||
JobToken AddJob(JobDescriptor const& descriptor, Lambda&& lambda);
|
||||
|
||||
template <typename... Lambdas>
|
||||
AZStd::fixed_vector<JobToken, sizeof...(Lambdas)> AddJobs(JobDescriptor const& descriptor, Lambdas&&... lambdas);
|
||||
AZStd::array<JobToken, sizeof...(Lambdas)> AddJobs(JobDescriptor const& descriptor, Lambdas&&... lambdas);
|
||||
|
||||
// By default, you are responsible for retaining the JobGraph, indicating you promise that
|
||||
// this JobGraph will live as long as it takes for all constituent jobs to complete.
|
||||
@@ -103,6 +111,7 @@ namespace AZ
|
||||
// of the job graph is expected to rely on either indirection, or safe overwriting
|
||||
// of previously used memory to supply new data (this can even be done as the first
|
||||
// job in the graph).
|
||||
// NOTE: This operation is invalid if the graph is in-flight
|
||||
void Detach();
|
||||
|
||||
// Invoke the job graph, asserting if there are dependency violations. Note that
|
||||
@@ -122,6 +131,7 @@ namespace AZ
|
||||
|
||||
private:
|
||||
friend class JobToken;
|
||||
friend class Internal::CompiledJobGraph;
|
||||
|
||||
Internal::CompiledJobGraph* m_compiledJobGraph = nullptr;
|
||||
|
||||
@@ -132,7 +142,7 @@ namespace AZ
|
||||
|
||||
uint32_t m_linkCount = 0;
|
||||
bool m_retained = true;
|
||||
bool m_submitted = false;
|
||||
AZStd::atomic<bool> m_submitted = false;
|
||||
};
|
||||
} // namespace AZ
|
||||
|
||||
|
||||
@@ -22,15 +22,19 @@ namespace AZ
|
||||
(PrecedesInternal(tokens), ...);
|
||||
}
|
||||
|
||||
template <typename... JT>
|
||||
inline void JobToken::Succeeds(JT&... tokens)
|
||||
{
|
||||
(tokens.PrecedesInternal(*this), ...);
|
||||
}
|
||||
|
||||
inline bool JobGraphEvent::IsSignaled()
|
||||
{
|
||||
AZ_Assert(m_submitted, "Querying the status of a job graph event that was never submitted along with the jobgraph");
|
||||
return m_semaphore.try_acquire_for(AZStd::chrono::milliseconds{ 0 });
|
||||
}
|
||||
|
||||
inline void JobGraphEvent::Wait()
|
||||
{
|
||||
AZ_Assert(m_submitted, "Waiting on a job graph event that was never submitted along with the jobgraph");
|
||||
m_semaphore.acquire();
|
||||
}
|
||||
|
||||
@@ -42,7 +46,7 @@ namespace AZ
|
||||
template<typename Lambda>
|
||||
inline JobToken JobGraph::AddJob(JobDescriptor const& desc, Lambda&& lambda)
|
||||
{
|
||||
AZ_Assert(!m_submitted, "Cannot mutate a JobGraph that was previously submitted.");
|
||||
AZ_Assert(!m_submitted, "Cannot mutate a JobGraph that was previously submitted or in flight.");
|
||||
|
||||
m_jobs.emplace_back(desc, AZStd::forward<Lambda>(lambda));
|
||||
|
||||
@@ -50,9 +54,9 @@ namespace AZ
|
||||
}
|
||||
|
||||
template <typename... Lambdas>
|
||||
inline AZStd::fixed_vector<JobToken, sizeof...(Lambdas)> AddJobs(JobDescriptor const& descriptor, Lambdas&&... lambdas)
|
||||
inline AZStd::array<JobToken, sizeof...(Lambdas)> JobGraph::AddJobs(JobDescriptor const& descriptor, Lambdas&&... lambdas)
|
||||
{
|
||||
return { AddJob(descriptor, lambdas)... };
|
||||
return { AddJob(descriptor, AZStd::forward<Lambdas>(lambdas))... };
|
||||
}
|
||||
|
||||
inline void JobGraph::Detach()
|
||||
|
||||
@@ -336,8 +336,7 @@ namespace UnitTest
|
||||
// d
|
||||
|
||||
a.Precedes(b, c);
|
||||
b.Precedes(d);
|
||||
c.Precedes(d);
|
||||
d.Succeeds(b, c);
|
||||
|
||||
JobGraphEvent ev;
|
||||
graph.SubmitOnExecutor(*m_executor, &ev);
|
||||
@@ -487,8 +486,7 @@ namespace UnitTest
|
||||
a.Precedes(b, c);
|
||||
b.Precedes(d);
|
||||
c.Precedes(e, f);
|
||||
e.Precedes(g);
|
||||
f.Precedes(g);
|
||||
g.Succeeds(e, f);
|
||||
g.Precedes(d);
|
||||
|
||||
JobGraphEvent ev;
|
||||
@@ -511,338 +509,94 @@ namespace Benchmark
|
||||
class JobGraphBenchmarkFixture : public ::benchmark::Fixture
|
||||
{
|
||||
public:
|
||||
static const int32_t LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH = 1;
|
||||
static const int32_t MEDIUM_WEIGHT_JOB_CALCULATE_PI_DEPTH = 1024;
|
||||
static const int32_t HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH = 1048576;
|
||||
|
||||
static const int32_t SMALL_NUMBER_OF_JOBS = 10;
|
||||
static const int32_t MEDIUM_NUMBER_OF_JOBS = 1024;
|
||||
static const int32_t LARGE_NUMBER_OF_JOBS = 16384;
|
||||
static AZStd::atomic<int32_t> s_numIncompleteJobs;
|
||||
|
||||
int m_depth = 1;
|
||||
JobGraph* graphs;
|
||||
|
||||
void SetUp(benchmark::State&) override
|
||||
{
|
||||
s_numIncompleteJobs = 0;
|
||||
|
||||
m_executor = aznew JobExecutor(0);
|
||||
graphs = new JobGraph[4];
|
||||
|
||||
// Generate some random priorities
|
||||
m_randomPriorities.resize(LARGE_NUMBER_OF_JOBS);
|
||||
std::mt19937_64 randomPriorityGenerator(1); // Always use the same seed
|
||||
std::uniform_int_distribution<> randomPriorityDistribution(0, static_cast<uint8_t>(AZ::JobPriority::PRIORITY_COUNT));
|
||||
std::generate(
|
||||
m_randomPriorities.begin(), m_randomPriorities.end(),
|
||||
[&randomPriorityDistribution, &randomPriorityGenerator]()
|
||||
{
|
||||
return randomPriorityDistribution(randomPriorityGenerator);
|
||||
});
|
||||
|
||||
// Generate some random depths
|
||||
m_randomDepths.resize(LARGE_NUMBER_OF_JOBS);
|
||||
std::mt19937_64 randomDepthGenerator(1); // Always use the same seed
|
||||
std::uniform_int_distribution<> randomDepthDistribution(
|
||||
LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH, HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
std::generate(
|
||||
m_randomDepths.begin(), m_randomDepths.end(),
|
||||
[&randomDepthDistribution, &randomDepthGenerator]()
|
||||
{
|
||||
return randomDepthDistribution(randomDepthGenerator);
|
||||
});
|
||||
|
||||
for (size_t i = 0; i != 4; ++i)
|
||||
{
|
||||
graphs[i].AddJob(
|
||||
descriptors[i],
|
||||
[this]
|
||||
{
|
||||
benchmark::DoNotOptimize(CalculatePi(m_depth));
|
||||
--s_numIncompleteJobs;
|
||||
});
|
||||
}
|
||||
executor = new JobExecutor;
|
||||
graph = new JobGraph;
|
||||
}
|
||||
|
||||
void TearDown(benchmark::State&) override
|
||||
{
|
||||
delete[] graphs;
|
||||
azdestroy(m_executor);
|
||||
m_randomDepths = {};
|
||||
m_randomPriorities = {};
|
||||
delete graph;
|
||||
delete executor;
|
||||
}
|
||||
|
||||
JobDescriptor descriptors[4] = { { "critical", "benchmark", JobPriority::CRITICAL },
|
||||
{ "high", "benchmark", JobPriority::HIGH },
|
||||
{ "mediium", "benchmark", JobPriority::MEDIUM },
|
||||
{ "medium", "benchmark", JobPriority::MEDIUM },
|
||||
{ "low", "benchmark", JobPriority::LOW } };
|
||||
|
||||
static inline double CalculatePi(AZ::u32 depth)
|
||||
{
|
||||
double pi = 0.0;
|
||||
for (AZ::u32 i = 0; i < depth; ++i)
|
||||
{
|
||||
const double numerator = static_cast<double>(((i % 2) * 2) - 1);
|
||||
const double denominator = static_cast<double>((2 * i) - 1);
|
||||
pi += numerator / denominator;
|
||||
}
|
||||
return (pi - 1.0) * 4;
|
||||
}
|
||||
|
||||
void RunCalculatePiJob(int32_t depth, int8_t priority)
|
||||
{
|
||||
m_depth = depth;
|
||||
++s_numIncompleteJobs;
|
||||
|
||||
graphs[priority].SubmitOnExecutor(*m_executor);
|
||||
}
|
||||
|
||||
void RunMultipleCalculatePiJobsWithDefaultPriority(uint32_t numberOfJobs, int32_t depth)
|
||||
{
|
||||
for (size_t i = 0; i != numberOfJobs; ++i)
|
||||
{
|
||||
RunCalculatePiJob(depth, 2);
|
||||
}
|
||||
|
||||
while (s_numIncompleteJobs > 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void RunMultipleCalculatePiJobsWithRandomPriority(uint32_t numberOfJobs, int32_t depth)
|
||||
{
|
||||
for (size_t i = 0; i != numberOfJobs; ++i)
|
||||
{
|
||||
RunCalculatePiJob(depth, m_randomPriorities[i]);
|
||||
}
|
||||
|
||||
while (s_numIncompleteJobs > 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void RunMultipleCalculatePiJobsWithRandomDepthAndDefaultPriority(uint32_t numberOfJobs)
|
||||
{
|
||||
for (size_t i = 0; i != numberOfJobs; ++i)
|
||||
{
|
||||
RunCalculatePiJob(m_randomDepths[i], 0);
|
||||
}
|
||||
|
||||
while (s_numIncompleteJobs > 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void RunMultipleCalculatePiJobsWithRandomDepthAndRandomPriority(uint32_t numberOfJobs)
|
||||
{
|
||||
for (size_t i = 0; i != numberOfJobs; ++i)
|
||||
{
|
||||
RunCalculatePiJob(m_randomDepths[i], m_randomPriorities[i]);
|
||||
}
|
||||
|
||||
while (s_numIncompleteJobs > 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
JobExecutor* m_executor;
|
||||
AZStd::vector<AZ::u32> m_randomDepths;
|
||||
AZStd::vector<AZ::s8> m_randomPriorities;
|
||||
JobGraph* graph;
|
||||
JobExecutor* executor;
|
||||
};
|
||||
|
||||
AZStd::atomic<int32_t> JobGraphBenchmarkFixture::s_numIncompleteJobs = 0;
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfLightWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, QueueToDequeue)(benchmark::State& state)
|
||||
{
|
||||
graph->AddJob(
|
||||
descriptors[2],
|
||||
[]
|
||||
{
|
||||
});
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(SMALL_NUMBER_OF_JOBS, LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
JobGraphEvent ev;
|
||||
graph->SubmitOnExecutor(*executor, &ev);
|
||||
ev.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfLightWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, OneAfterAnother)(benchmark::State& state)
|
||||
{
|
||||
auto a = graph->AddJob(
|
||||
descriptors[2],
|
||||
[]
|
||||
{
|
||||
});
|
||||
auto b = graph->AddJob(
|
||||
descriptors[2],
|
||||
[]
|
||||
{
|
||||
});
|
||||
a.Precedes(b);
|
||||
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(MEDIUM_NUMBER_OF_JOBS, LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
JobGraphEvent ev;
|
||||
graph->SubmitOnExecutor(*executor, &ev);
|
||||
ev.Wait();
|
||||
}
|
||||
executor->Drain();
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfLightWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, FourToOneJoin)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(LARGE_NUMBER_OF_JOBS, LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
auto [a, b, c, d, e] = graph->AddJobs(
|
||||
descriptors[2],
|
||||
[]
|
||||
{
|
||||
},
|
||||
[]
|
||||
{
|
||||
},
|
||||
[]
|
||||
{
|
||||
},
|
||||
[]
|
||||
{
|
||||
},
|
||||
[]
|
||||
{
|
||||
});
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfMediumWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(SMALL_NUMBER_OF_JOBS, MEDIUM_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
e.Succeeds(a, b, c, d);
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfMediumWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(MEDIUM_NUMBER_OF_JOBS, MEDIUM_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfMediumWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(LARGE_NUMBER_OF_JOBS, MEDIUM_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfHeavyWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(SMALL_NUMBER_OF_JOBS, HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfHeavyWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(MEDIUM_NUMBER_OF_JOBS, HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfHeavyWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithDefaultPriority(LARGE_NUMBER_OF_JOBS, HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfRandomWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomDepthAndDefaultPriority(SMALL_NUMBER_OF_JOBS);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfRandomWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomDepthAndDefaultPriority(MEDIUM_NUMBER_OF_JOBS);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfRandomWeightJobsWithDefaultPriority)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomDepthAndDefaultPriority(LARGE_NUMBER_OF_JOBS);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfLightWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(SMALL_NUMBER_OF_JOBS, LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfLightWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(MEDIUM_NUMBER_OF_JOBS, LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfLightWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(LARGE_NUMBER_OF_JOBS, LIGHT_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfMediumWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(SMALL_NUMBER_OF_JOBS, MEDIUM_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfMediumWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(MEDIUM_NUMBER_OF_JOBS, MEDIUM_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfMediumWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(LARGE_NUMBER_OF_JOBS, MEDIUM_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfHeavyWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(SMALL_NUMBER_OF_JOBS, HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfHeavyWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(MEDIUM_NUMBER_OF_JOBS, HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfHeavyWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomPriority(LARGE_NUMBER_OF_JOBS, HEAVY_WEIGHT_JOB_CALCULATE_PI_DEPTH);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunSmallNumberOfRandomWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomDepthAndRandomPriority(SMALL_NUMBER_OF_JOBS);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunMediumNumberOfRandomWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomDepthAndRandomPriority(MEDIUM_NUMBER_OF_JOBS);
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK_F(JobGraphBenchmarkFixture, RunLargeNumberOfRandomWeightJobsWithRandomPriorities)(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
RunMultipleCalculatePiJobsWithRandomDepthAndRandomPriority(LARGE_NUMBER_OF_JOBS);
|
||||
JobGraphEvent ev;
|
||||
graph->SubmitOnExecutor(*executor, &ev);
|
||||
ev.Wait();
|
||||
}
|
||||
executor->Drain();
|
||||
}
|
||||
} // namespace Benchmark
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user