2021-05-25 13:27:48 -04:00
|
|
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
#include "DerivedDataBuildScheduler.h"
|
2021-09-09 06:04:00 -04:00
|
|
|
#include "Containers/RingBuffer.h"
|
2021-05-25 13:27:48 -04:00
|
|
|
#include "DerivedDataBuildFunction.h"
|
|
|
|
|
#include "DerivedDataBuildFunctionFactory.h"
|
|
|
|
|
#include "DerivedDataBuildFunctionRegistry.h"
|
2021-08-06 15:49:14 -04:00
|
|
|
#include "DerivedDataRequest.h"
|
|
|
|
|
#include "DerivedDataRequestOwner.h"
|
2021-09-27 09:31:49 -04:00
|
|
|
#include "Experimental/Async/LazyEvent.h"
|
2021-05-25 13:27:48 -04:00
|
|
|
#include "Misc/Guid.h"
|
2021-09-09 06:04:00 -04:00
|
|
|
#include "Misc/ScopeLock.h"
|
2021-05-25 13:27:48 -04:00
|
|
|
|
|
|
|
|
namespace UE::DerivedData::Private
|
|
|
|
|
{
|
|
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
2021-09-09 06:04:00 -04:00
|
|
|
|
|
|
|
|
static void ScheduleAsyncStep(IBuildJob& Job, IRequestOwner& Owner, const TCHAR* DebugName)
|
|
|
|
|
{
|
2022-03-23 16:56:39 -04:00
|
|
|
Owner.LaunchTask(DebugName, [&Job] { Job.StepExecution(); });
|
2021-09-09 06:04:00 -04:00
|
|
|
}
|
|
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
2021-09-09 06:04:00 -04:00
|
|
|
|
|
|
|
|
/** Limits simultaneous build jobs to reduce peak memory usage */
|
|
|
|
|
class FMemoryScheduler
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FMemoryScheduler();
|
|
|
|
|
~FMemoryScheduler();
|
|
|
|
|
|
|
|
|
|
void RegisterRunningJob(uint64 MemoryEstimate);
|
|
|
|
|
void StepAsyncOrQueue(uint64 MemoryEstimate, IBuildJob& Job, IRequestOwner& Owner, const TCHAR* DebugName);
|
|
|
|
|
void RegisterEndedJob(uint64 MemoryEstimate);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
/** Handles waiting and cancellation while a job is queued up */
|
|
|
|
|
class FRequest final : public FRequestBase
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FRequest(FMemoryScheduler& InScheduler, IBuildJob& InJob, IRequestOwner& InOwner, uint64 InMemoryEstimate);
|
|
|
|
|
~FRequest() { ensure(!TryClaimEnd()); }
|
2021-09-09 13:36:28 -04:00
|
|
|
|
|
|
|
|
void SetPriority(EPriority Priority) final {}
|
|
|
|
|
void Wait() final { Event.Wait(); }
|
|
|
|
|
void Cancel() final;
|
2021-09-09 06:04:00 -04:00
|
|
|
|
|
|
|
|
uint64 GetMemoryEstimate() const { return MemoryEstimate; }
|
|
|
|
|
bool TryClaimEnd() { return !bClaimed.test_and_set(); }
|
|
|
|
|
void End(const TCHAR* DebugName);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
FMemoryScheduler& Scheduler;
|
|
|
|
|
IBuildJob& Job;
|
|
|
|
|
IRequestOwner& Owner;
|
|
|
|
|
const uint64 MemoryEstimate;
|
2021-09-27 09:31:49 -04:00
|
|
|
UE::FLazyEvent Event{EEventMode::ManualReset};
|
2021-09-09 06:04:00 -04:00
|
|
|
std::atomic_flag bClaimed = ATOMIC_FLAG_INIT;
|
|
|
|
|
};
|
2021-09-09 13:36:28 -04:00
|
|
|
|
2021-09-09 06:04:00 -04:00
|
|
|
const uint64 TotalPhysical;
|
|
|
|
|
const uint64 AvailablePhysicalAtStartup;
|
|
|
|
|
const uint64 MaxMemoryUsage;
|
2021-09-09 13:36:28 -04:00
|
|
|
|
2021-09-09 06:04:00 -04:00
|
|
|
FCriticalSection CriticalSection;
|
|
|
|
|
TRingBuffer<TRefCountPtr<FRequest>> Queue;
|
|
|
|
|
uint64 TotalScheduledMemory = 0;
|
|
|
|
|
uint64 TotalScheduledWatermark = 0;
|
|
|
|
|
|
|
|
|
|
// @pre CriticalSection locked
|
|
|
|
|
bool CanRunNow(uint64 MemoryEstimate) const
|
|
|
|
|
{
|
|
|
|
|
return TotalScheduledMemory == 0 || TotalScheduledMemory + MemoryEstimate < MaxMemoryUsage;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
FMemoryScheduler::FRequest::FRequest(
|
|
|
|
|
FMemoryScheduler& InScheduler,
|
|
|
|
|
IBuildJob& InJob,
|
|
|
|
|
IRequestOwner& InOwner,
|
|
|
|
|
uint64 InMemoryEstimate)
|
|
|
|
|
: Scheduler(InScheduler)
|
|
|
|
|
, Job(InJob)
|
|
|
|
|
, Owner(InOwner)
|
|
|
|
|
, MemoryEstimate(InMemoryEstimate)
|
2021-09-09 06:04:00 -04:00
|
|
|
{
|
|
|
|
|
Owner.Begin(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMemoryScheduler::FRequest::End(const TCHAR* DebugName)
|
|
|
|
|
{
|
2021-09-09 13:36:28 -04:00
|
|
|
Owner.End(this, [this, DebugName]
|
|
|
|
|
{
|
|
|
|
|
ScheduleAsyncStep(Job, Owner, DebugName);
|
|
|
|
|
Event.Trigger();
|
|
|
|
|
});
|
2021-09-09 06:04:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMemoryScheduler::FRequest::Cancel()
|
|
|
|
|
{
|
|
|
|
|
if (TryClaimEnd())
|
|
|
|
|
{
|
|
|
|
|
// Add estimated memory to simplify implementation, even though memory won't be allocated.
|
|
|
|
|
// FBuildJobSchedule::EndJob() will restore the scheduler's available memory.
|
|
|
|
|
// Might require optimization if lots of queued jobs are cancelled at the same time.
|
|
|
|
|
Scheduler.RegisterRunningJob(MemoryEstimate);
|
|
|
|
|
End(TEXT("MemoryQueueCancel"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FMemoryScheduler::FMemoryScheduler()
|
2021-09-09 13:36:28 -04:00
|
|
|
: TotalPhysical(FPlatformMemory::GetStats().TotalPhysical)
|
|
|
|
|
, AvailablePhysicalAtStartup(FPlatformMemory::GetStats().AvailablePhysical)
|
|
|
|
|
, MaxMemoryUsage(TotalPhysical / 8 + AvailablePhysicalAtStartup / 2)
|
2021-09-09 06:04:00 -04:00
|
|
|
{
|
|
|
|
|
Queue.Reserve(128);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FMemoryScheduler::~FMemoryScheduler()
|
|
|
|
|
{
|
|
|
|
|
ensure(Queue.IsEmpty());
|
|
|
|
|
ensure(TotalScheduledMemory == 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMemoryScheduler::RegisterRunningJob(uint64 MemoryEstimate)
|
|
|
|
|
{
|
|
|
|
|
check(MemoryEstimate);
|
|
|
|
|
|
|
|
|
|
FScopeLock Lock(&CriticalSection);
|
|
|
|
|
TotalScheduledMemory += MemoryEstimate;
|
|
|
|
|
TotalScheduledWatermark = FMath::Max(TotalScheduledWatermark, TotalScheduledMemory);
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
void FMemoryScheduler::StepAsyncOrQueue(
|
|
|
|
|
uint64 MemoryEstimate,
|
|
|
|
|
IBuildJob& Job,
|
|
|
|
|
IRequestOwner& Owner,
|
|
|
|
|
const TCHAR* DebugName)
|
2021-09-09 06:04:00 -04:00
|
|
|
{
|
|
|
|
|
check(MemoryEstimate);
|
|
|
|
|
{
|
|
|
|
|
FScopeLock Lock(&CriticalSection);
|
|
|
|
|
|
|
|
|
|
if (!CanRunNow(MemoryEstimate))
|
|
|
|
|
{
|
2021-09-09 13:36:28 -04:00
|
|
|
Queue.Emplace(new FRequest(*this, Job, Owner, MemoryEstimate));
|
2021-09-09 06:04:00 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TotalScheduledMemory += MemoryEstimate;
|
|
|
|
|
TotalScheduledWatermark = FMath::Max(TotalScheduledWatermark, TotalScheduledMemory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ScheduleAsyncStep(Job, Owner, DebugName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FMemoryScheduler::RegisterEndedJob(uint64 DoneEstimate)
|
|
|
|
|
{
|
|
|
|
|
if (DoneEstimate)
|
|
|
|
|
{
|
|
|
|
|
TArray<TRefCountPtr<FRequest>, TInlineAllocator<16>> Continuations;
|
2021-09-09 13:36:28 -04:00
|
|
|
|
2021-09-09 06:04:00 -04:00
|
|
|
{
|
|
|
|
|
FScopeLock Lock(&CriticalSection);
|
|
|
|
|
|
|
|
|
|
TotalScheduledMemory -= DoneEstimate;
|
|
|
|
|
|
|
|
|
|
if (Queue.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-09-09 13:36:28 -04:00
|
|
|
|
2021-09-09 06:04:00 -04:00
|
|
|
while (Queue.Num() && CanRunNow(Queue.First()->GetMemoryEstimate()))
|
|
|
|
|
{
|
|
|
|
|
if (Queue.First()->TryClaimEnd())
|
|
|
|
|
{
|
|
|
|
|
TotalScheduledMemory += Queue.First()->GetMemoryEstimate();
|
|
|
|
|
Continuations.Add(Queue.First());
|
|
|
|
|
}
|
|
|
|
|
Queue.PopFront();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TotalScheduledWatermark = FMath::Max(TotalScheduledWatermark, TotalScheduledMemory);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const TRefCountPtr<FRequest>& Request : Continuations)
|
|
|
|
|
{
|
|
|
|
|
Request->End(TEXT("MemoryQueueContinue"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
2021-09-09 06:04:00 -04:00
|
|
|
|
|
|
|
|
class FBuildJobSchedule final : public IBuildJobSchedule
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
FBuildJobSchedule(IBuildJob& InJob, IRequestOwner& InOwner, FMemoryScheduler& InMemoryLimiter)
|
|
|
|
|
: Job(InJob)
|
|
|
|
|
, Owner(InOwner)
|
|
|
|
|
, MemoryLimiter(InMemoryLimiter)
|
2021-09-09 13:36:28 -04:00
|
|
|
{
|
|
|
|
|
}
|
2021-09-09 06:04:00 -04:00
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
FBuildSchedulerParams& EditParameters() final { return Params; }
|
2021-09-09 06:04:00 -04:00
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
void ScheduleCacheQuery() final { StepSync(); }
|
|
|
|
|
void ScheduleCacheStore() final { StepSync(); }
|
|
|
|
|
void ScheduleResolveKey() final { StepAsync(TEXT("ResolveKey")); }
|
|
|
|
|
void ScheduleResolveInputMeta() final { StepAsync(TEXT("ResolveInputMeta")); }
|
|
|
|
|
void ScheduleResolveInputData() final { StepAsyncOrQueue(TEXT("ResolveInputData")); }
|
|
|
|
|
void ScheduleExecuteRemote() final { StepAsyncOrQueue(TEXT("ExecuteRemote")); }
|
|
|
|
|
void ScheduleExecuteLocal() final { StepAsyncOrQueue(TEXT("ExecuteLocal")); }
|
2021-09-09 06:04:00 -04:00
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
void EndJob() final { MemoryLimiter.RegisterEndedJob(ScheduledMemoryEstimate); }
|
2021-09-09 06:04:00 -04:00
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void StepSync()
|
|
|
|
|
{
|
|
|
|
|
Job.StepExecution();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StepAsync(const TCHAR* DebugName)
|
|
|
|
|
{
|
|
|
|
|
if (Owner.GetPriority() == EPriority::Blocking)
|
|
|
|
|
{
|
|
|
|
|
StepSync();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ScheduleAsyncStep(Job, Owner, DebugName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void StepAsyncOrQueue(const TCHAR* DebugName)
|
|
|
|
|
{
|
|
|
|
|
check(Params.TotalRequiredMemory >= Params.ResolvedInputsSize);
|
|
|
|
|
const uint64 CurrentMemoryEstimate = Params.TotalRequiredMemory - Params.ResolvedInputsSize;
|
|
|
|
|
|
|
|
|
|
// Only queue for memory once
|
|
|
|
|
if (ScheduledMemoryEstimate || CurrentMemoryEstimate == 0)
|
|
|
|
|
{
|
|
|
|
|
StepAsync(DebugName);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ScheduledMemoryEstimate = CurrentMemoryEstimate;
|
|
|
|
|
|
|
|
|
|
if (Owner.GetPriority() == EPriority::Blocking)
|
|
|
|
|
{
|
|
|
|
|
MemoryLimiter.RegisterRunningJob(ScheduledMemoryEstimate);
|
|
|
|
|
StepSync();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
MemoryLimiter.StepAsyncOrQueue(ScheduledMemoryEstimate, Job, Owner, DebugName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
IBuildJob& Job;
|
|
|
|
|
IRequestOwner& Owner;
|
|
|
|
|
FBuildSchedulerParams Params;
|
|
|
|
|
FMemoryScheduler& MemoryLimiter;
|
|
|
|
|
uint64 ScheduledMemoryEstimate = 0;
|
|
|
|
|
};
|
|
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
2021-09-09 06:04:00 -04:00
|
|
|
|
2021-05-25 13:27:48 -04:00
|
|
|
class FBuildScheduler final : public IBuildScheduler
|
|
|
|
|
{
|
2021-09-09 13:36:28 -04:00
|
|
|
TUniquePtr<IBuildJobSchedule> BeginJob(IBuildJob& Job, IRequestOwner& Owner) final
|
2021-09-09 06:04:00 -04:00
|
|
|
{
|
|
|
|
|
return MakeUnique<FBuildJobSchedule>(Job, Owner, MemoryLimiter);
|
|
|
|
|
}
|
2021-08-04 17:52:40 -04:00
|
|
|
|
2021-09-09 06:04:00 -04:00
|
|
|
FMemoryScheduler MemoryLimiter;
|
2021-05-25 13:27:48 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
IBuildScheduler* CreateBuildScheduler()
|
|
|
|
|
{
|
|
|
|
|
return new FBuildScheduler();
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-09 13:36:28 -04:00
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
2021-09-09 06:04:00 -04:00
|
|
|
|
2021-05-25 13:27:48 -04:00
|
|
|
} // UE::DerivedData::Private
|