Files
UnrealEngine/Engine/Source/Programs/AutoRTFMTests/Private/Benchmarks.cpp
2025-05-18 13:04:45 +08:00

753 lines
15 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "AutoRTFMTesting.h"
#include "Catch2Includes.h"
#include <vector>
#include <unordered_set>
using namespace std;
template<typename T>
class FNode
{
public:
FNode(T Value) : Value(Value) { }
T GetValue() const { return Value; }
void AddEdge(FNode* Target)
{
Edges.push_back(Target);
}
const vector<FNode*>& GetEdges() const { return Edges; }
private:
vector<FNode*> Edges;
T Value;
};
template<typename T>
class FGraph
{
public:
FGraph() = default;
FGraph(const FGraph&) = delete;
FGraph& operator=(const FGraph&) = delete;
FGraph(FGraph&& InGraph)
{
*this = std::move(InGraph);
}
FGraph& operator=(FGraph&& InGraph)
{
Reset();
Nodes = std::move(InGraph.Nodes);
Roots = std::move(InGraph.Roots);
return *this;
}
~FGraph()
{
Reset();
}
void Reset()
{
for (FNode<T>* Node : Nodes)
{
delete Node;
}
Nodes.clear();
Roots.clear();
}
FNode<T>* AddNode(T Value)
{
FNode<T>* Node = new FNode<T>(Value);
Nodes.push_back(Node);
return Node;
}
void AddRoot(FNode<T>* Node)
{
Roots.push_back(Node);
}
const vector<FNode<T>*>& GetNodes() const { return Nodes; }
const vector<FNode<T>*>& GetRoots() const { return Roots; }
template<typename TFunc>
void DepthFirstSearchPre(const TFunc& Func) const
{
vector<FNode<T>*> Worklist;
unordered_set<FNode<T>*> Seen;
auto Push = [&](FNode<T>* Node)
{
if (Seen.insert(Node).second)
{
Worklist.push_back(Node);
}
};
auto PushAll = [&](const vector<FNode<T>*>& InnerNodes)
{
for (FNode<T>* Node : InnerNodes)
{
Push(Node);
}
};
PushAll(Roots);
while (!Worklist.empty())
{
FNode<T>* Node = Worklist.back();
Worklist.pop_back();
Func(Node);
PushAll(Node->GetEdges());
}
}
private:
vector<FNode<T>*> Nodes;
vector<FNode<T>*> Roots;
};
unsigned XorshiftState;
void ResetXorshift()
{
XorshiftState = 666;
}
unsigned Xorshift()
{
unsigned Value = XorshiftState;
Value ^= Value << 13;
Value ^= Value >> 17;
Value ^= Value << 5;
XorshiftState = Value;
return Value;
}
unsigned BadRandom(unsigned Limit)
{
return Xorshift() % Limit; // Yes I know this isn't great.
}
void AddToGraph(FGraph<unsigned>& Graph)
{
auto RandomNode = [&](unsigned Value)
{
FNode<unsigned>* Result = Graph.AddNode(Value);
if (!BadRandom(Graph.GetNodes().size() + 1))
{
Graph.AddRoot(Result);
}
return Result;
};
FNode<unsigned>* A = RandomNode(Xorshift());
FNode<unsigned>* B = RandomNode(Xorshift());
FNode<unsigned>* C = RandomNode(Xorshift());
FNode<unsigned>* D = RandomNode(Xorshift());
FNode<unsigned>* E = RandomNode(Xorshift());
FNode<unsigned>* F = RandomNode(Xorshift());
FNode<unsigned>* G = RandomNode(Xorshift());
auto RandomEdge = [&](FNode<unsigned>* From, FNode<unsigned>* To)
{
if (!BadRandom(5))
{
From = Graph.GetNodes()[BadRandom(Graph.GetNodes().size())];
}
if (!BadRandom(5))
{
To = Graph.GetNodes()[BadRandom(Graph.GetNodes().size())];
}
From->AddEdge(To);
};
RandomEdge(A, B);
RandomEdge(B, C);
RandomEdge(C, B);
RandomEdge(C, D);
RandomEdge(B, D);
RandomEdge(D, E);
RandomEdge(E, F);
RandomEdge(E, G);
RandomEdge(A, E);
RandomEdge(A, G);
RandomEdge(G, E);
}
FGraph<unsigned> BuildGraph(const unsigned Total = 1000)
{
FGraph<unsigned> Result;
for (unsigned Count = Total; Count--;)
{
AddToGraph(Result);
}
return Result;
}
unsigned WalkGraph(const FGraph<unsigned>& Graph)
{
unsigned Result = 0;
unsigned Count = 0;
Graph.DepthFirstSearchPre([&](FNode<unsigned>* Node)
{
Result += Node->GetValue();
Count++;
});
//printf("Saw %zu roots, %u nodes, sum is %u.\n", Graph.GetRoots().size(), Count, Result);
return Result;
}
void CheckResult(unsigned Result, unsigned Total)
{
switch (Total)
{
case 10000: REQUIRE(Result == 434344629); break;
case 100: REQUIRE(Result == 3732096243); break;
case 10: REQUIRE(Result == 3524276090); break;
case 1: REQUIRE(Result == 2218159753); break;
default: abort();
}
}
TEST_CASE("Graph")
{
{
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph.AddNode(42); });
auto& Nodes = Graph.GetNodes();
REQUIRE(Nodes.size() == 1);
REQUIRE(Nodes[0]->GetValue() == 42);
}
{
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph.AddRoot(Graph.AddNode(42)); });
auto& Roots = Graph.GetRoots();
REQUIRE(Roots.size() == 1);
REQUIRE(Roots[0]->GetValue() == 42);
auto& Nodes = Graph.GetNodes();
REQUIRE(Nodes.size() == 1);
REQUIRE(Nodes[0]->GetValue() == 42);
}
for (unsigned Total : {1, 10, 100, 10000})
{
#if defined(__has_feature)
#if __has_feature(address_sanitizer)
// This test is hella long with ASan so we bail after the first iteration.
if (Total > 1)
{
return;
}
#endif
#endif
{
unsigned Result;
ResetXorshift();
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph = BuildGraph(Total); });
Result = WalkGraph(Graph);
CheckResult(Result, Total);
}
{
unsigned Result;
ResetXorshift();
FGraph<unsigned> Graph = BuildGraph(Total);
AutoRTFM::Commit([&]() { Result = WalkGraph(Graph); });
CheckResult(Result, Total);
}
{
unsigned Result;
ResetXorshift();
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph = BuildGraph(Total); });
AutoRTFM::Commit([&]() { Result = WalkGraph(Graph); });
CheckResult(Result, Total);
}
{
unsigned Result;
ResetXorshift();
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph = BuildGraph(Total); Result = WalkGraph(Graph); });
CheckResult(Result, Total);
}
}
BENCHMARK("build non transactional / walk non transactional")
{
ResetXorshift();
FGraph<unsigned> Graph = BuildGraph();
WalkGraph(Graph);
};
BENCHMARK("build transactional / walk non transactional")
{
ResetXorshift();
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph = BuildGraph(); });
WalkGraph(Graph);
};
BENCHMARK("build non transactional / walk transactional")
{
ResetXorshift();
FGraph<unsigned> Graph = BuildGraph();
AutoRTFM::Commit([&]() { WalkGraph(Graph); });
};
BENCHMARK("build transactional / walk transactional")
{
ResetXorshift();
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph = BuildGraph(); });
AutoRTFM::Commit([&]() { WalkGraph(Graph); });
};
BENCHMARK("build + walk transactional")
{
ResetXorshift();
FGraph<unsigned> Graph;
AutoRTFM::Commit([&]() { Graph = BuildGraph(); WalkGraph(Graph); });
};
}
TEST_CASE("Benchmarks.PopOnAbortHandler")
{
constexpr unsigned Count = 16 * 128;
BENCHMARK("PopOnAbortSingleKey")
{
bool bHit = false;
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHit, [&] { bHit = true; });
AutoRTFM::PopOnAbortHandler(&bHit);
}
AutoRTFM::AbortTransaction();
});
REQUIRE(!bHit);
};
BENCHMARK("PopOnAbortMultiKey")
{
bool bHits[Count] = { false };
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHits[Index], [&bHits, Index] { bHits[Index] = true; });
AutoRTFM::PopOnAbortHandler(&bHits[Index]);
}
AutoRTFM::AbortTransaction();
});
for (unsigned int Index = 0; Index < Count; Index++)
{
REQUIRE(!bHits[Index]);
}
};
BENCHMARK("PopOnAbortShortSingleKey")
{
bool bHit = false;
unsigned AbortCounter = 0;
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnAbort([&] { AbortCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHit, [&] { bHit = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnAbortHandler(&bHit);
}
AutoRTFM::AbortTransaction();
});
REQUIRE(!bHit);
REQUIRE(Count == AbortCounter);
};
BENCHMARK("PopOnAbortLongSingleKey")
{
bool bHit = false;
unsigned AbortCounter = 0;
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHit, [&] { bHit = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnAbort([&] { AbortCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnAbortHandler(&bHit);
}
AutoRTFM::AbortTransaction();
});
REQUIRE(!bHit);
REQUIRE(Count == AbortCounter);
};
BENCHMARK("PopOnAbortShortMultiKey")
{
bool bHits[Count] = { false };
unsigned AbortCounter = 0;
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnAbort([&] { AbortCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHits[Index], [&bHits, Index] { bHits[Index] = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnAbortHandler(&bHits[Index]);
}
AutoRTFM::AbortTransaction();
});
for (unsigned int Index = 0; Index < Count; Index++)
{
REQUIRE(!bHits[Index]);
}
REQUIRE(Count == AbortCounter);
};
BENCHMARK("PopOnAbortLongMultiKey")
{
bool bHits[Count] = { false };
unsigned AbortCounter = 0;
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHits[Index], [&bHits, Index] { bHits[Index] = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnAbort([&] { AbortCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnAbortHandler(&bHits[Index]);
}
AutoRTFM::AbortTransaction();
});
for (unsigned int Index = 0; Index < Count; Index++)
{
REQUIRE(!bHits[Index]);
}
REQUIRE(Count == AbortCounter);
};
}
TEST_CASE("Benchmarks.PopAllOnAbortHandlers")
{
constexpr unsigned Count = 16 * 128;
BENCHMARK("Pop")
{
bool bHit = false;
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHit, [&] { bHit = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnAbortHandler(&bHit);
}
AutoRTFM::AbortTransaction();
});
REQUIRE(!bHit);
};
BENCHMARK("PopAll")
{
bool bHit = false;
AutoRTFM::Testing::Abort([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnAbortHandler(&bHit, [&] { bHit = true; });
}
AutoRTFM::PopAllOnAbortHandlers(&bHit);
AutoRTFM::AbortTransaction();
});
REQUIRE(!bHit);
};
}
TEST_CASE("Benchmarks.PopOnCommitHandler")
{
constexpr unsigned int Count = 16 * 128;
BENCHMARK("PopOnCommitSingleKey")
{
bool bHit = false;
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHit, [&] { bHit = true; });
AutoRTFM::PopOnCommitHandler(&bHit);
}
});
REQUIRE(!bHit);
};
BENCHMARK("PopOnCommitMultiKey")
{
bool bHits[Count] = { false };
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHits[Index], [&bHits, Index] { bHits[Index] = true; });
AutoRTFM::PopOnCommitHandler(&bHits[Index]);
}
});
for (unsigned int Index = 0; Index < Count; Index++)
{
REQUIRE(!bHits[Index]);
}
};
BENCHMARK("PopOnCommitShortSingleKey")
{
bool bHit = false;
unsigned int CallbackCounter = 0;
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnCommit([&] { CallbackCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHit, [&] { bHit = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnCommitHandler(&bHit);
}
});
REQUIRE(!bHit);
REQUIRE(Count == CallbackCounter);
};
BENCHMARK("PopOnCommitLongSingleKey")
{
bool bHit = false;
unsigned int CallbackCounter = 0;
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHit, [&] { bHit = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnCommit([&] { CallbackCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnCommitHandler(&bHit);
}
});
REQUIRE(!bHit);
REQUIRE(Count == CallbackCounter);
};
BENCHMARK("PopOnCommitShortMultiKey")
{
bool bHits[Count] = { false };
unsigned int CallbackCounter = 0;
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnCommit([&] { CallbackCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHits[Index], [&bHits, Index] { bHits[Index] = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnCommitHandler(&bHits[Index]);
}
});
for (unsigned int Index = 0; Index < Count; Index++)
{
REQUIRE(!bHits[Index]);
}
REQUIRE(Count == CallbackCounter);
};
BENCHMARK("PopOnCommitLongMultiKey")
{
bool bHits[Count] = { false };
unsigned int CallbackCounter = 0;
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHits[Index], [&bHits, Index] { bHits[Index] = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::OnCommit([&] { CallbackCounter++; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnCommitHandler(&bHits[Index]);
}
});
for (unsigned int Index = 0; Index < Count; Index++)
{
REQUIRE(!bHits[Index]);
}
REQUIRE(Count == CallbackCounter);
};
}
TEST_CASE("Benchmarks.PopAllOnCommitHandlers")
{
constexpr unsigned int Count = 16 * 128;
BENCHMARK("Pop")
{
bool bHit = false;
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHit, [&] { bHit = true; });
}
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PopOnCommitHandler(&bHit);
}
});
REQUIRE(!bHit);
};
BENCHMARK("PopAll")
{
bool bHit = false;
AutoRTFM::Testing::Commit([&]
{
for (unsigned int Index = 0; Index < Count; Index++)
{
AutoRTFM::PushOnCommitHandler(&bHit, [&] { bHit = true; });
}
AutoRTFM::PopAllOnCommitHandlers(&bHit);
});
REQUIRE(!bHit);
};
}
TEST_CASE("Benchmarks.TopLevelTransaction")
{
constexpr int Iterations = 100;
BENCHMARK("Commit")
{
int Counter = 0;
for (int Index = 0; Index < Iterations; ++Index)
{
AutoRTFM::Testing::Commit([&]
{
++Counter;
});
}
REQUIRE(Counter == Iterations);
};
BENCHMARK("Abort")
{
int Counter = 0;
for (int Index = 0; Index < Iterations; ++Index)
{
AutoRTFM::Testing::Abort([&]
{
++Counter;
AutoRTFM::AbortTransaction();
});
}
REQUIRE(Counter == 0);
};
}