Positional container: performance tests
This commit is contained in:
@@ -37,6 +37,11 @@ static inline bool equalEpsilon(const T &a, const T &b) {
|
|||||||
struct Any {};
|
struct Any {};
|
||||||
|
|
||||||
template <typename T, size_t N, typename Tag = Any> class vec {
|
template <typename T, size_t N, typename Tag = Any> class vec {
|
||||||
|
|
||||||
|
// Friend declaration for move constructor from different tag types
|
||||||
|
template <typename U, size_t M, typename OtherTag>
|
||||||
|
friend class vec;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
vec() : m_Array{} {}
|
vec() : m_Array{} {}
|
||||||
|
|
||||||
@@ -143,6 +148,13 @@ public:
|
|||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
friend vec operator/(const vec &a, const vec& b) {
|
||||||
|
vec<T, N, Tag> c;
|
||||||
|
std::ranges::transform(a.m_Array, b.m_Array,
|
||||||
|
c.m_Array.begin(), std::divides{});
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// compound-assignment operators
|
// compound-assignment operators
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -138,11 +138,6 @@ public:
|
|||||||
const WorldPos corner_1 = center + radius;
|
const WorldPos corner_1 = center + radius;
|
||||||
const WorldPos corner_2 = center - radius;
|
const WorldPos corner_2 = center - radius;
|
||||||
|
|
||||||
if (!CheckBounds(corner_1) || !CheckBounds(corner_2))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto A = GetCoords(corner_1);
|
const auto A = GetCoords(corner_1);
|
||||||
const auto B = GetCoords(corner_2);
|
const auto B = GetCoords(corner_2);
|
||||||
|
|
||||||
@@ -154,12 +149,30 @@ public:
|
|||||||
size_t y_min = static_cast<size_t>(std::floor(y_min_f));
|
size_t y_min = static_cast<size_t>(std::floor(y_min_f));
|
||||||
size_t y_max = static_cast<size_t>(std::ceil(y_max_f));
|
size_t y_max = static_cast<size_t>(std::ceil(y_max_f));
|
||||||
|
|
||||||
// TODO this goes through more positions than we need
|
|
||||||
for (size_t x = x_min; x <= x_max; x++)
|
for (size_t x = x_min; x <= x_max; x++)
|
||||||
{
|
{
|
||||||
for (size_t y = y_min; y <= y_max; y++)
|
for (size_t y = y_min; y <= y_max; y++)
|
||||||
{
|
{
|
||||||
|
if (!CheckBounds(x, y))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
|
// TODO this is approx 2x faster, but inserts items outside of radius;
|
||||||
|
// We can use this is a Get(rectangle) function
|
||||||
std::ranges::copy(m_Grid[x][y], std::back_inserter(output_vec));
|
std::ranges::copy(m_Grid[x][y], std::back_inserter(output_vec));
|
||||||
|
#else
|
||||||
|
for (auto item_wptr : m_Grid[x][y])
|
||||||
|
{
|
||||||
|
if (auto shared = item_wptr.lock())
|
||||||
|
{
|
||||||
|
if (center.DistanceTo(shared->GetPosition()) < radius)
|
||||||
|
{
|
||||||
|
output_vec.push_back(item_wptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,14 +215,21 @@ private:
|
|||||||
|
|
||||||
coord_type GetCoords(const WorldPos &wp)
|
coord_type GetCoords(const WorldPos &wp)
|
||||||
{
|
{
|
||||||
auto coord_float = wp / m_ChunksPerAxis;
|
auto coord_float = wp / m_GridStep.ChangeTag<WorldPos>();
|
||||||
return coord_type{
|
return coord_type{
|
||||||
static_cast<size_t>(coord_float.x()),
|
static_cast<size_t>(coord_float.x()),
|
||||||
static_cast<size_t>(coord_float.y())
|
static_cast<size_t>(coord_float.y())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CheckBounds(const WorldPos& pos)
|
bool CheckBounds(size_t x, size_t y) const
|
||||||
|
{
|
||||||
|
bool x_in_bounds = x < m_Grid.size();
|
||||||
|
bool y_in_bounds = y < m_Grid.size();
|
||||||
|
return x_in_bounds && y_in_bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CheckBounds(const WorldPos& pos) const
|
||||||
{
|
{
|
||||||
auto [x,y] = pos;
|
auto [x,y] = pos;
|
||||||
bool x_in_bounds = 0.0f < x && x < m_GridSize.x();
|
bool x_in_bounds = 0.0f < x && x < m_GridSize.x();
|
||||||
|
|||||||
@@ -2,14 +2,12 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
#include "positional_container.hpp"
|
#include "positional_container.hpp"
|
||||||
|
|
||||||
// TODO: Add necessary includes for collision testing
|
|
||||||
// #include "collision_shapes.hpp"
|
|
||||||
// #include "entities.hpp"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file collision_performance.cpp
|
* @file collision_performance.cpp
|
||||||
* @brief Performance tests for collision detection systems
|
* @brief Performance tests for collision detection systems
|
||||||
@@ -83,48 +81,171 @@ void benchmark_function(const std::string& name, int iterations, Func func) {
|
|||||||
*/
|
*/
|
||||||
class Dummy {
|
class Dummy {
|
||||||
public:
|
public:
|
||||||
Dummy() : m_Position(0.0f, 0.0f) {}
|
Dummy() : m_Position{0.0f, 0.0f}, m_Id(next_id++) {}
|
||||||
Dummy(float x, float y) : m_Position(x, y) {}
|
Dummy(float x, float y) : m_Position{x, y}, m_Id(next_id++) {}
|
||||||
Dummy(WorldPos pos) : m_Position(pos) {}
|
Dummy(WorldPos pos) : m_Position(pos), m_Id(next_id++) {}
|
||||||
|
|
||||||
WorldPos GetPosition() const { return m_Position; }
|
WorldPos GetPosition() const { return m_Position; }
|
||||||
void SetPosition(WorldPos pos) { m_Position = pos; }
|
void SetPosition(WorldPos pos) { m_Position = pos; }
|
||||||
|
int GetId() const { return m_Id; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WorldPos m_Position;
|
WorldPos m_Position;
|
||||||
|
int m_Id;
|
||||||
|
static int next_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Example test function 1
|
int Dummy::next_id = 0;
|
||||||
void test_function_1() {
|
|
||||||
|
|
||||||
PositionalContainer<Dummy> pos_cont{WorldSize{10.0f, 10.0f}, 10};
|
/**
|
||||||
SimpleContainer<Dummy> simp_cont;
|
* @brief Helper function to generate random float in range [min, max]
|
||||||
|
*/
|
||||||
// TODO: Implement actual collision test
|
float random_float(std::mt19937& gen, float min, float max) {
|
||||||
volatile int sum = 0;
|
std::uniform_real_distribution<float> dist(min, max);
|
||||||
for (int i = 0; i < 1000; ++i) {
|
return dist(gen);
|
||||||
sum += i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Example test function 2
|
/**
|
||||||
void test_function_2() {
|
* @brief Compare two sets of weak_ptrs by comparing the IDs of the objects they point to
|
||||||
// TODO: Implement actual collision test
|
*/
|
||||||
volatile int product = 1;
|
bool compare_results(const std::vector<std::weak_ptr<Dummy>>& a,
|
||||||
for (int i = 1; i < 100; ++i) {
|
const std::vector<std::weak_ptr<Dummy>>& b) {
|
||||||
product *= (i % 10 + 1);
|
std::set<int> ids_a, ids_b;
|
||||||
|
|
||||||
|
for (const auto& weak : a) {
|
||||||
|
if (auto shared = weak.lock()) {
|
||||||
|
ids_a.insert(shared->GetId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& weak : b) {
|
||||||
|
if (auto shared = weak.lock()) {
|
||||||
|
ids_b.insert(shared->GetId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids_a == ids_b;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(CollisionPerformance, CompareAlgorithms) {
|
TEST(CollisionPerformance, CompareContainers) {
|
||||||
std::cout << "\n=== Collision Performance Comparison ===\n" << std::endl;
|
std::cout << "\n=== Collision Performance Comparison ===\n" << std::endl;
|
||||||
|
|
||||||
const int iterations = 10000;
|
// Configuration
|
||||||
|
const int NUM_OBJECTS = 1000;
|
||||||
|
const int NUM_LOOKUPS = 100;
|
||||||
|
const float WORLD_SIZE = 1000.0f;
|
||||||
|
const float LOOKUP_RADIUS = 50.0f;
|
||||||
|
const size_t CHUNKS = 20;
|
||||||
|
|
||||||
benchmark_function("Algorithm 1 (test_function_1)", iterations, test_function_1);
|
// Random number generator
|
||||||
benchmark_function("Algorithm 2 (test_function_2)", iterations, test_function_2);
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
|
||||||
|
// Create containers
|
||||||
|
PositionalContainer<Dummy> pos_cont{WorldSize{WORLD_SIZE, WORLD_SIZE}, CHUNKS};
|
||||||
|
SimpleContainer<Dummy> simp_cont;
|
||||||
|
|
||||||
|
// Create and add dummy objects with random positions
|
||||||
|
std::vector<std::shared_ptr<Dummy>> objects;
|
||||||
|
objects.reserve(NUM_OBJECTS);
|
||||||
|
|
||||||
|
std::cout << "Creating " << NUM_OBJECTS << " objects with random positions..." << std::endl;
|
||||||
|
for (int i = 0; i < NUM_OBJECTS; ++i) {
|
||||||
|
float x = random_float(gen, 10.0f, WORLD_SIZE - 10.0f);
|
||||||
|
float y = random_float(gen, 10.0f, WORLD_SIZE - 10.0f);
|
||||||
|
auto obj = std::make_shared<Dummy>(x, y);
|
||||||
|
objects.push_back(obj);
|
||||||
|
pos_cont.Add(obj);
|
||||||
|
simp_cont.Add(obj);
|
||||||
|
}
|
||||||
|
std::cout << "Objects created and added to containers." << std::endl;
|
||||||
|
|
||||||
|
// Generate random lookup positions
|
||||||
|
std::vector<WorldPos> lookup_positions;
|
||||||
|
lookup_positions.reserve(NUM_LOOKUPS);
|
||||||
|
for (int i = 0; i < NUM_LOOKUPS; ++i) {
|
||||||
|
float x = random_float(gen, 0.0f, WORLD_SIZE);
|
||||||
|
float y = random_float(gen, 0.0f, WORLD_SIZE);
|
||||||
|
lookup_positions.push_back(WorldPos{x, y});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Benchmark SimpleContainer
|
||||||
|
double simple_total_time = 0.0;
|
||||||
|
std::vector<std::vector<std::weak_ptr<Dummy>>> simple_results;
|
||||||
|
simple_results.reserve(NUM_LOOKUPS);
|
||||||
|
|
||||||
|
std::cout << "\nBenchmarking SimpleContainer with " << NUM_LOOKUPS << " lookups..." << std::endl;
|
||||||
|
for (const auto& pos : lookup_positions) {
|
||||||
|
auto start = PerformanceTimer::Clock::now();
|
||||||
|
auto result = simp_cont.Get(pos, LOOKUP_RADIUS);
|
||||||
|
auto end = PerformanceTimer::Clock::now();
|
||||||
|
|
||||||
|
PerformanceTimer::Duration duration = end - start;
|
||||||
|
simple_total_time += duration.count();
|
||||||
|
simple_results.push_back(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
double simple_avg_time = simple_total_time / NUM_LOOKUPS;
|
||||||
|
std::cout << std::fixed << std::setprecision(6)
|
||||||
|
<< "[BENCHMARK] SimpleContainer:\n"
|
||||||
|
<< " Total time: " << simple_total_time << " ms\n"
|
||||||
|
<< " Average time per lookup: " << simple_avg_time << " ms\n"
|
||||||
|
<< " Throughput: " << (NUM_LOOKUPS / (simple_total_time / 1000.0))
|
||||||
|
<< " lookups/sec" << std::endl;
|
||||||
|
|
||||||
|
// Benchmark PositionalContainer
|
||||||
|
double positional_total_time = 0.0;
|
||||||
|
std::vector<std::vector<std::weak_ptr<Dummy>>> positional_results;
|
||||||
|
positional_results.reserve(NUM_LOOKUPS);
|
||||||
|
|
||||||
|
std::cout << "\nBenchmarking PositionalContainer with " << NUM_LOOKUPS << " lookups..." << std::endl;
|
||||||
|
for (const auto& pos : lookup_positions) {
|
||||||
|
auto start = PerformanceTimer::Clock::now();
|
||||||
|
auto result = pos_cont.Get(pos, LOOKUP_RADIUS);
|
||||||
|
auto end = PerformanceTimer::Clock::now();
|
||||||
|
|
||||||
|
PerformanceTimer::Duration duration = end - start;
|
||||||
|
positional_total_time += duration.count();
|
||||||
|
positional_results.push_back(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
double positional_avg_time = positional_total_time / NUM_LOOKUPS;
|
||||||
|
std::cout << std::fixed << std::setprecision(6)
|
||||||
|
<< "[BENCHMARK] PositionalContainer:\n"
|
||||||
|
<< " Total time: " << positional_total_time << " ms\n"
|
||||||
|
<< " Average time per lookup: " << positional_avg_time << " ms\n"
|
||||||
|
<< " Throughput: " << (NUM_LOOKUPS / (positional_total_time / 1000.0))
|
||||||
|
<< " lookups/sec" << std::endl;
|
||||||
|
|
||||||
|
// Verify results match
|
||||||
|
std::cout << "\nVerifying results correctness..." << std::endl;
|
||||||
|
int mismatches = 0;
|
||||||
|
for (size_t i = 0; i < NUM_LOOKUPS; ++i) {
|
||||||
|
if (!compare_results(simple_results[i], positional_results[i])) {
|
||||||
|
mismatches++;
|
||||||
|
std::cout << "Mismatch at lookup " << i
|
||||||
|
<< " (pos: " << lookup_positions[i] << ")" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mismatches == 0) {
|
||||||
|
std::cout << "✓ All " << NUM_LOOKUPS << " lookups produced identical results!" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "✗ Found " << mismatches << " mismatches out of "
|
||||||
|
<< NUM_LOOKUPS << " lookups" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Performance comparison
|
||||||
|
std::cout << "\n=== Performance Summary ===\n";
|
||||||
|
double speedup = simple_avg_time / positional_avg_time;
|
||||||
|
std::cout << std::fixed << std::setprecision(2)
|
||||||
|
<< "PositionalContainer is " << speedup << "x "
|
||||||
|
<< (speedup > 1.0 ? "faster" : "slower")
|
||||||
|
<< " than SimpleContainer" << std::endl;
|
||||||
|
|
||||||
std::cout << "\n======================================\n" << std::endl;
|
std::cout << "\n======================================\n" << std::endl;
|
||||||
|
|
||||||
SUCCEED();
|
// Assertions
|
||||||
|
EXPECT_EQ(mismatches, 0) << "Results should match between containers";
|
||||||
|
EXPECT_GT(speedup, 1.0) << "PositionalContainer should be faster than SimpleContainer";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user