Files
UnrealEngineUWP/Engine/Source/Developer/DerivedDataCache/Private/DerivedDataCacheStoreAsync.cpp
devin doucette 0e929ad2f3 DDC: Re-added the in-flight cache for async puts
- Legacy puts are now executing asynchronously.
- Memory cache now merges partial cache records.
- Memory cache is used to store data temporarily while it is being written by an async put.
- Expanded the deprecation of persisted boot/memory caches, which no longer offer the performance benefit that they used to.
- Fixed the pak file cache to skip data with a compressed size of over 2 GiB.

#jira UE-141307
#preflight 620d85f93609e19371510fb1
#lockdown Aurel.Cordonnier
#rb Zousar.Shaker
#rnx

#ROBOMERGE-OWNER: Devin.Doucette
#ROBOMERGE-AUTHOR: devin.doucette
#ROBOMERGE-COMMAND: _robomerge UE5-Main
#ROBOMERGE-SOURCE: CL 19076205 in //UE5/Release-5.0/... via CL 19094550
#ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v921-19075845)

[CL 19095974 by devin doucette in ue5-main branch]
2022-02-23 13:40:18 -05:00

349 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Async/AsyncWork.h"
#include "DerivedDataBackendInterface.h"
#include "DerivedDataCachePrivate.h"
#include "DerivedDataCacheStore.h"
#include "DerivedDataCacheUsageStats.h"
#include "DerivedDataLegacyCacheStore.h"
#include "DerivedDataRequest.h"
#include "DerivedDataRequestOwner.h"
#include "DerivedDataValueId.h"
#include "Experimental/Async/LazyEvent.h"
#include "Memory/SharedBuffer.h"
#include "MemoryCacheStore.h"
#include "ProfilingDebugging/CookStats.h"
#include "Stats/Stats.h"
#include "Tasks/Task.h"
#include "Templates/Invoke.h"
namespace UE::DerivedData
{
/**
* A cache store that executes non-blocking requests in a dedicated thread pool.
*
* Puts can be stored in a memory cache while they are in flight.
*/
class FCacheStoreAsync : public ILegacyCacheStore
{
public:
FCacheStoreAsync(ILegacyCacheStore* InnerCache, ECacheStoreFlags InnerFlags, IMemoryCacheStore* MemoryCache);
virtual void Put(
TConstArrayView<FCachePutRequest> Requests,
IRequestOwner& Owner,
FOnCachePutComplete&& OnComplete) override
{
if (MemoryCache)
{
MemoryCache->Put(Requests, Owner, [](auto&&){});
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimePut,) Requests, Owner,
[this, OnComplete = MoveTemp(OnComplete)](FCachePutResponse&& Response)
{
MemoryCache->Delete(Response.Key);
OnComplete(MoveTemp(Response));
}, &ICacheStore::Put);
}
else
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimePut,) Requests, Owner, MoveTemp(OnComplete), &ICacheStore::Put);
}
}
virtual void Get(
TConstArrayView<FCacheGetRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetComplete&& OnComplete) override
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimeGet,) Requests, Owner, MoveTemp(OnComplete), &ICacheStore::Get);
}
virtual void PutValue(
TConstArrayView<FCachePutValueRequest> Requests,
IRequestOwner& Owner,
FOnCachePutValueComplete&& OnComplete) override
{
if (MemoryCache)
{
MemoryCache->PutValue(Requests, Owner, [](auto&&){});
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimePut,) Requests, Owner,
[this, OnComplete = MoveTemp(OnComplete)](FCachePutValueResponse&& Response)
{
MemoryCache->DeleteValue(Response.Key);
OnComplete(MoveTemp(Response));
}, &ICacheStore::PutValue);
}
else
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimePut,) Requests, Owner, MoveTemp(OnComplete), &ICacheStore::PutValue);
}
}
virtual void GetValue(
TConstArrayView<FCacheGetValueRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetValueComplete&& OnComplete) override
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimeGet,) Requests, Owner, MoveTemp(OnComplete), &ICacheStore::GetValue);
}
virtual void GetChunks(
TConstArrayView<FCacheGetChunkRequest> Requests,
IRequestOwner& Owner,
FOnCacheGetChunkComplete&& OnComplete) override
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimeGet,) Requests, Owner, MoveTemp(OnComplete), &ICacheStore::GetChunks);
}
virtual void LegacyPut(
TConstArrayView<FLegacyCachePutRequest> Requests,
IRequestOwner& Owner,
FOnLegacyCachePutComplete&& OnComplete) override
{
if (MemoryCache)
{
MemoryCache->LegacyPut(Requests, Owner, [](auto&&){});
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimePut,) Requests, Owner,
[this, OnComplete = MoveTemp(OnComplete)](FLegacyCachePutResponse&& Response)
{
MemoryCache->LegacyDelete(Response.Key);
OnComplete(MoveTemp(Response));
}, &ILegacyCacheStore::LegacyPut);
}
else
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimePut,) Requests, Owner, MoveTemp(OnComplete), &ILegacyCacheStore::LegacyPut);
}
}
virtual void LegacyGet(
TConstArrayView<FLegacyCacheGetRequest> Requests,
IRequestOwner& Owner,
FOnLegacyCacheGetComplete&& OnComplete) override
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimeGet,) Requests, Owner, MoveTemp(OnComplete), &ILegacyCacheStore::LegacyGet);
}
virtual void LegacyDelete(
TConstArrayView<FLegacyCacheDeleteRequest> Requests,
IRequestOwner& Owner,
FOnLegacyCacheDeleteComplete&& OnComplete) override
{
Execute(COOK_STAT(&FDerivedDataCacheUsageStats::TimePut,) Requests, Owner, MoveTemp(OnComplete), &ILegacyCacheStore::LegacyDelete);
}
virtual void LegacyStats(FDerivedDataCacheStatsNode& OutNode) override;
virtual bool LegacyDebugOptions(FBackendDebugOptions& Options) override
{
return InnerCache->LegacyDebugOptions(Options);
}
private:
COOK_STAT(using CookStatsFunction = FCookStats::FScopedStatsCounter (FDerivedDataCacheUsageStats::*)());
template <typename RequestType, typename OnCompleteType, typename OnExecuteType>
void Execute(
COOK_STAT(CookStatsFunction OnAddStats,)
TConstArrayView<RequestType> Requests,
IRequestOwner& Owner,
OnCompleteType&& OnComplete,
OnExecuteType&& OnExecute);
ILegacyCacheStore* InnerCache;
IMemoryCacheStore* MemoryCache;
FDerivedDataCacheUsageStats UsageStats;
ECacheStoreFlags InnerFlags;
};
FCacheStoreAsync::FCacheStoreAsync(ILegacyCacheStore* InInnerCache, ECacheStoreFlags InInnerFlags, IMemoryCacheStore* InMemoryCache)
: InnerCache(InInnerCache)
, MemoryCache(InMemoryCache)
, InnerFlags(InInnerFlags)
{
check(InnerCache);
}
void FCacheStoreAsync::LegacyStats(FDerivedDataCacheStatsNode& OutNode)
{
OutNode = {TEXT("Async"), TEXT(""), EnumHasAnyFlags(InnerFlags, ECacheStoreFlags::Local)};
OutNode.Stats.Add(TEXT(""), UsageStats);
InnerCache->LegacyStats(OutNode.Children.Add_GetRef(MakeShared<FDerivedDataCacheStatsNode>()).Get());
if (MemoryCache)
{
MemoryCache->LegacyStats(OutNode.Children.Add_GetRef(MakeShared<FDerivedDataCacheStatsNode>()).Get());
}
}
class FDerivedDataAsyncWrapperRequest final : public FRequestBase, private IQueuedWork
{
public:
inline FDerivedDataAsyncWrapperRequest(
IRequestOwner& InOwner,
TUniqueFunction<void (IRequestOwner& Owner, bool bCancel)>&& InFunction)
: Owner(InOwner)
, Function(MoveTemp(InFunction))
{
}
inline void Start(EPriority Priority)
{
Owner.Begin(this);
DoneEvent.Reset();
Private::GCacheThreadPool->AddQueuedWork(this, GetPriority(Priority));
}
inline void Execute(bool bCancel)
{
FScopeCycleCounter Scope(GetStatId(), /*bAlways*/ true);
Owner.End(this, [this, bCancel]
{
Function(Owner, bCancel);
DoneEvent.Trigger();
});
// DO NOT ACCESS ANY MEMBERS PAST THIS POINT!
}
// IRequest Interface
inline void SetPriority(EPriority Priority) final
{
if (Private::GCacheThreadPool->RetractQueuedWork(this))
{
Private::GCacheThreadPool->AddQueuedWork(this, GetPriority(Priority));
}
}
inline void Cancel() final
{
if (!DoneEvent.Wait(0))
{
if (Private::GCacheThreadPool->RetractQueuedWork(this))
{
Abandon();
}
else
{
FScopeCycleCounter Scope(GetStatId());
DoneEvent.Wait();
}
}
}
inline void Wait() final
{
if (!DoneEvent.Wait(0))
{
if (Private::GCacheThreadPool->RetractQueuedWork(this))
{
DoThreadedWork();
}
else
{
FScopeCycleCounter Scope(GetStatId());
DoneEvent.Wait();
}
}
}
private:
static EQueuedWorkPriority GetPriority(EPriority Priority)
{
switch (Priority)
{
case EPriority::Blocking: return EQueuedWorkPriority::Blocking;
case EPriority::Highest: return EQueuedWorkPriority::Highest;
case EPriority::High: return EQueuedWorkPriority::High;
case EPriority::Normal: return EQueuedWorkPriority::Normal;
case EPriority::Low: return EQueuedWorkPriority::Low;
case EPriority::Lowest: return EQueuedWorkPriority::Lowest;
default: checkNoEntry(); return EQueuedWorkPriority::Normal;
}
}
// IQueuedWork Interface
inline void DoThreadedWork() final
{
Execute(/*bCancel*/ false);
}
inline void Abandon() final
{
Execute(/*bCancel*/ true);
}
inline TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FDerivedDataAsyncWrapperRequest, STATGROUP_ThreadPoolAsyncTasks);
}
private:
IRequestOwner& Owner;
TUniqueFunction<void (IRequestOwner& Owner, bool bCancel)> Function;
FLazyEvent DoneEvent{EEventMode::ManualReset};
};
void Private::ExecuteInCacheThreadPool(
IRequestOwner& Owner,
TUniqueFunction<void (IRequestOwner& Owner, bool bCancel)>&& Function)
{
FDerivedDataAsyncWrapperRequest* Request = new FDerivedDataAsyncWrapperRequest(Owner, MoveTemp(Function));
Request->Start(Owner.GetPriority());
}
template <typename RequestType, typename OnCompleteType, typename OnExecuteType>
void FCacheStoreAsync::Execute(
COOK_STAT(CookStatsFunction OnAddStats,)
const TConstArrayView<RequestType> Requests,
IRequestOwner& Owner,
OnCompleteType&& OnComplete,
OnExecuteType&& OnExecute)
{
auto ExecuteWithStats = [this, COOK_STAT(OnAddStats,) OnExecute](TConstArrayView<RequestType> Requests, IRequestOwner& Owner, OnCompleteType&& OnComplete) mutable
{
Invoke(OnExecute, *InnerCache, Requests, Owner, [this, COOK_STAT(OnAddStats,) OnComplete = MoveTemp(OnComplete)](auto&& Response)
{
if (Response.Status == EStatus::Ok)
{
COOK_STAT(Invoke(OnAddStats, UsageStats).AddHit(0));
}
OnComplete(MoveTemp(Response));
FDerivedDataBackend::Get().AddToAsyncCompletionCounter(-1);
});
};
FDerivedDataBackend::Get().AddToAsyncCompletionCounter(Requests.Num());
if (Owner.GetPriority() == EPriority::Blocking || !Private::GCacheThreadPool)
{
return ExecuteWithStats(Requests, Owner, MoveTemp(OnComplete));
}
Private::ExecuteInCacheThreadPool(Owner,
[this,
Requests = TArray<RequestType>(Requests),
OnComplete = MoveTemp(OnComplete),
ExecuteWithStats = MoveTemp(ExecuteWithStats)]
(IRequestOwner& Owner, bool bCancel) mutable
{
if (!bCancel)
{
ExecuteWithStats(Requests, Owner, MoveTemp(OnComplete));
}
else
{
CompleteWithStatus(Requests, MoveTemp(OnComplete), EStatus::Canceled);
FDerivedDataBackend::Get().AddToAsyncCompletionCounter(-Requests.Num());
}
});
}
ILegacyCacheStore* CreateCacheStoreAsync(ILegacyCacheStore* InnerCache, ECacheStoreFlags InnerFlags, IMemoryCacheStore* MemoryCache)
{
return new FCacheStoreAsync(InnerCache, InnerFlags, MemoryCache);
}
} // UE::DerivedData