About caching facts
The UFactsComponent
includes an optional, built-in LRU (Least Recently Used) cache for its facts. This is a performance optimization feature designed to speed up repeated read access to fact data.
1. Purpose and Mechanism
Purpose: To reduce the cost of retrieving a fact's value.
While looking up a fact in the component's internal TMap
is generally fast, it's not free. The FFactCache
provides a small, extremely fast layer of memory that stores the most recently accessed facts.
Mechanism:
-
On
GetFact()
:- The component first attempts to find the requested
FFactTag
in theFFactCache
. - Cache Hit: If found, the value is returned immediately, avoiding any further lookups. This is the optimal path.
- Cache Miss: If not found, the component proceeds to look up the fact in its primary data stores (
ReplicatedFacts
orLocalFacts
containers). - Once the fact is found in the primary store, it is added to the cache before being returned. If the cache is full, the least recently used item is evicted to make room.
- The component first attempts to find the requested
-
On
UpdateFact()
:- The value is updated in the primary data store (
FFactContainer
). - The component then explicitly calls
FactCache.Invalidate(FactTag)
. This is crucial to prevent the cache from serving stale, outdated data on subsequent reads.
- The value is updated in the primary data store (
2. Configuration
The cache is configured directly on the UFactsComponent
via two properties:
bEnableFactsCache
(bool, default:true
): A simple boolean to turn the entire caching system on or off for that component instance.MaxCacheSize
(int32, default:128
): The maximum number of fact instances that the cache can hold.
These can be set per-Blueprint in the component's Details panel or directly in C++ code.
3. Usage Guidelines
The cache is not a magic bullet. Understanding its trade-offs is key to using it effectively.
Ideal Scenarios for the Cache:
- High Read Frequency: The cache provides the most benefit for facts that are read many times per frame or during a specific gameplay loop (e.g., checking a player's health, speed, and status flags during a combat calculation).
- Large Number of Facts: Components with hundreds of defined facts will see a greater relative performance improvement from cache hits compared to those with only a few.
- Performance-Critical Actors: For actors that are known performance hotspots, ensuring the cache is enabled and appropriately sized can be a valuable optimization.
When to Consider Disabling or Adjusting the Cache:
- High Write Frequency: If a component's facts are updated more often than they are read, the cache can introduce a small amount of overhead. Every
UpdateFact
call results in a cache invalidation, and if the data is rarely read, the cache never gets a chance to provide a benefit. For these "write-heavy" components, disabling the cache (bEnableFactsCache = false
) might be a micro-optimization. - Minimal Fact Count: For components with a very small number of facts (e.g., less than 10), the performance gain from the cache is likely negligible and may not be worth the memory overhead.
- Strict Memory Constraints: While the cache's memory footprint is small, on platforms with very tight memory budgets, you may choose to disable it on non-essential actors to save every possible byte.
4. Monitoring and Debugging
This section explains the usage for a specific component. You can look in the Diagnostics Panel > Performance Panel section for an overview.
You can monitor the effectiveness of a component's cache at runtime to make informed optimization decisions.
-
UFactsComponent::GetCacheStats()
: This function returns anFFactCacheStats
struct, which contains:Hits
: The number of times a fact was successfully found in the cache.Misses
: The number of times a fact was not found, requiring a full lookup.HitRate
: A float between 0.0 and 1.0 representing the percentage of reads that were cache hits.
-
UFactRuntimeSubsystem::GetAggregatedCacheStats()
: This provides a convenient way to see the combined statistics for all registeredUFactsComponent
instances in the world.
A high hit rate (e.g., > 0.90) indicates the cache is performing effectively. A low hit rate may suggest that the cache size is too small for the working set of facts, or that the access patterns are too random for the cache to be beneficial.
C++ Example:
Here is a simple example of how you could check a component's cache stats for debugging:
#include "Components/FactsComponent.h"
#include "Utils/FactCache.h" // For FFactCacheStats
#include "Engine/Engine.h"
void AMyCharacter::PrintFactCacheStats()
{
if (UFactsComponent* FactsComp = FindComponentByClass<UFactsComponent>())
{
FFactCacheStats Stats = FactsComp->GetCacheStats();
float HitRatePercent = Stats.HitRate * 100.0f;
FString StatsString = FString::Printf(
TEXT("FactsComponent Cache Stats for %s: Hits=%lld, Misses=%lld, Hit Rate=%.2f%%"),
*GetName(),
Stats.Hits,
Stats.Misses,
HitRatePercent
);
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Cyan, StatsString);
}
}
}
Next Up: Tools & Debugging Guide