From acc03a75aaa1f71006b2aacb3e39b7b36208d854 Mon Sep 17 00:00:00 2001 From: NukeBird Date: Thu, 20 Feb 2025 14:35:22 +0300 Subject: [PATCH] Add benchmarks --- tests/zecsy.cpp | 468 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 437 insertions(+), 31 deletions(-) diff --git a/tests/zecsy.cpp b/tests/zecsy.cpp index a4e3672..60dbfc0 100644 --- a/tests/zecsy.cpp +++ b/tests/zecsy.cpp @@ -7,7 +7,7 @@ using namespace zecsy; -TEST_CASE("Create a single entity and verify its existence") +TEST_CASE("Create a single entity and verify its existence", "[test]") { world w; @@ -16,7 +16,8 @@ TEST_CASE("Create a single entity and verify its existence") REQUIRE(w.is_alive(e)); } -TEST_CASE("Destroy an entity and ensure it no longer exists in the world") +TEST_CASE("Destroy an entity and ensure it no longer exists in the world", + "[test]") { world w; @@ -26,7 +27,7 @@ TEST_CASE("Destroy an entity and ensure it no longer exists in the world") REQUIRE_FALSE(w.is_alive(e)); } -TEST_CASE("Entity #0 should be reserved and never used") +TEST_CASE("Entity #0 should be reserved and never used", "[test]") { world w; @@ -40,7 +41,8 @@ struct ChoosenOne { }; -TEST_CASE("Entity shouldn't have a component that wasn't attached to it") +TEST_CASE("Entity shouldn't have a component that wasn't attached to it", + "[test]") { world w; @@ -49,7 +51,7 @@ TEST_CASE("Entity shouldn't have a component that wasn't attached to it") REQUIRE_FALSE(w.has(e)); } -TEST_CASE("Attempt of getting non-owned component should throw") +TEST_CASE("Attempt of getting non-owned component should throw", "[test]") { world w; @@ -59,7 +61,8 @@ TEST_CASE("Attempt of getting non-owned component should throw") } TEST_CASE("Attach a simple component to an entity and verify it is correctly " - "associated") + "associated", + "[test]") { world w; @@ -80,7 +83,8 @@ struct Comp }; TEST_CASE("Retrieve a component from an entity and verify its data matches " - "what was set") + "what was set", + "[test]") { world w; @@ -95,7 +99,8 @@ TEST_CASE("Retrieve a component from an entity and verify its data matches " } TEST_CASE( - "Remove a component from an entity and verify it is no longer attached") + "Remove a component from an entity and verify it is no longer attached", + "[test]") { world w; @@ -111,7 +116,7 @@ TEST_CASE( REQUIRE_FALSE(w.has(e)); } -TEST_CASE("Addresses of removed components should be reused") +TEST_CASE("Addresses of removed components should be reused", "[test]") { world w; std::vector entities; @@ -156,7 +161,8 @@ TEST_CASE("Addresses of removed components should be reused") } TEST_CASE("Attach multiple components to an entity and verify all are " - "correctly stored and retrievable") + "correctly stored and retrievable", + "[test]") { world w; @@ -173,7 +179,8 @@ TEST_CASE("Attach multiple components to an entity and verify all are " } TEST_CASE("Create a simple system that processes entities with a specific " - "component and verify it executes correctly") + "component and verify it executes correctly", + "[test]") { struct Component { @@ -200,7 +207,8 @@ TEST_CASE("Create a simple system that processes entities with a specific " } TEST_CASE("Test a systems ability to query and process only entities with a " - "specific combination of components") + "specific combination of components", + "[test]") { struct C0 { @@ -226,12 +234,13 @@ TEST_CASE("Test a systems ability to query and process only entities with a " REQUIRE(w.get(e0).value == 0); REQUIRE(w.get(e0).value == 10); - w.query([e0](entity_id e, C0& c0, C1& c1) - { - REQUIRE(e == e0); - c0.value++; - c1.value++; - }); + w.query( + [e0](entity_id e, C0& c0, C1& c1) + { + REQUIRE(e == e0); + c0.value++; + c1.value++; + }); REQUIRE(w.get(e0).value == 1); REQUIRE(w.get(e0).value == 11); @@ -243,7 +252,7 @@ TEST_CASE("Test a systems ability to query and process only entities with a " REQUIRE_FALSE(w.has(e2)); } -TEST_CASE("Systems execute at correct frequencies") +TEST_CASE("Systems execute at correct frequencies", "[test]") { world w; system_scheduler scheduler; @@ -268,7 +277,7 @@ TEST_CASE("Systems execute at correct frequencies") REQUIRE(slow_count == 2); // 1 Hz system should execute 1 time } -TEST_CASE("Systems handle zero-frequency gracefully") +TEST_CASE("Systems handle zero-frequency gracefully", "[test]") { world w; system_scheduler scheduler; @@ -288,7 +297,7 @@ TEST_CASE("Systems handle zero-frequency gracefully") REQUIRE(zero_count == 0); } -TEST_CASE("Systems handle varying update rates") +TEST_CASE("Systems handle varying update rates", "[test]") { world w; system_scheduler scheduler; @@ -308,7 +317,7 @@ TEST_CASE("Systems handle varying update rates") REQUIRE(varying_count == 10); } -TEST_CASE("Systems handle large time steps") +TEST_CASE("Systems handle large time steps", "[test]") { world w; system_scheduler scheduler; @@ -325,7 +334,7 @@ TEST_CASE("Systems handle large time steps") REQUIRE(large_step_count == 2); } -TEST_CASE("Systems handle multiple frequencies") +TEST_CASE("Systems handle multiple frequencies", "[test]") { world w; system_scheduler scheduler; @@ -351,7 +360,7 @@ TEST_CASE("Systems handle multiple frequencies") REQUIRE(slow_count == 1); // 1 Hz system } -TEST_CASE("Systems handle fractional frequencies") +TEST_CASE("Systems handle fractional frequencies", "[test]") { world w; system_scheduler scheduler; @@ -372,7 +381,7 @@ TEST_CASE("Systems handle fractional frequencies") REQUIRE(fractional_count == 2); } -TEST_CASE("Systems handle zero delta time") +TEST_CASE("Systems handle zero delta time", "[test]") { world w; system_scheduler scheduler; @@ -389,7 +398,7 @@ TEST_CASE("Systems handle zero delta time") REQUIRE(zero_dt_count == 0); } -TEST_CASE("Systems handle negative delta time") +TEST_CASE("Systems handle negative delta time", "[test]") { world w; system_scheduler scheduler; @@ -410,7 +419,7 @@ TEST_CASE("Systems handle negative delta time") REQUIRE(count == 2); } -TEST_CASE("Entity count tracking") +TEST_CASE("Entity count tracking", "[test]") { world w; REQUIRE(w.entity_count() == 0); @@ -423,7 +432,7 @@ TEST_CASE("Entity count tracking") REQUIRE(w.entity_count() == 1); } -TEST_CASE("Component counting mechanisms") +TEST_CASE("Component counting mechanisms", "[test]") { struct Health { @@ -454,7 +463,7 @@ TEST_CASE("Component counting mechanisms") REQUIRE(w.total_component_count() == 1); } -TEST_CASE("Archetype signature management") +TEST_CASE("Archetype signature management", "[test]") { struct A { @@ -509,7 +518,7 @@ TEST_CASE("Archetype signature management") REQUIRE(w.archetype_count() == 0); } -TEST_CASE("Component distribution across archetypes") +TEST_CASE("Component distribution across archetypes", "[test]") { struct A { @@ -547,7 +556,7 @@ TEST_CASE("Component distribution across archetypes") REQUIRE(w.archetype_count() == 3); //, , } -TEST_CASE("Entity inspection") +TEST_CASE("Entity inspection", "[test]") { struct Transform { @@ -572,3 +581,400 @@ TEST_CASE("Entity inspection") w.remove(e); REQUIRE(w.components_in_entity(e) == 1); } + +namespace +{ + // Benchmark components + struct Position + { + float x, y; + }; + struct Velocity + { + float dx, dy; + }; + struct Health + { + int value; + }; + struct BigData + { + char buffer[4096]; + }; + + // Benchmark entity counts + constexpr int SMALL = 1'000; + constexpr int MEDIUM = 5'000; + constexpr int LARGE = 10'000; +} // namespace + +TEST_CASE("Core operations benchmarks", "[benchmark]") +{ + BENCHMARK_ADVANCED("Create entities [1000]")( + Catch::Benchmark::Chronometer meter) + { + meter.measure( + [&] + { + world w; + for(int i = 0; i < SMALL; ++i) + { + w.make_entity(); + } + return w.entity_count(); + }); + }; + + BENCHMARK_ADVANCED("Create entities [5000]")( + Catch::Benchmark::Chronometer meter) + { + meter.measure( + [&] + { + world w; + for(int i = 0; i < MEDIUM; ++i) + { + w.make_entity(); + } + return w.entity_count(); + }); + }; + + BENCHMARK_ADVANCED("Create entities [10000]")( + Catch::Benchmark::Chronometer meter) + { + meter.measure( + [&] + { + world w; + for(int i = 0; i < LARGE; ++i) + { + w.make_entity(); + } + return w.entity_count(); + }); + }; + + BENCHMARK_ADVANCED("Create entities with components [1000]")( + Catch::Benchmark::Chronometer meter) + { + meter.measure( + [&] + { + world w; + for(int i = 0; i < SMALL; ++i) + { + auto e = w.make_entity(); + w.set(e, {1.0f, 2.0f}); + w.set(e, {3.0f, 4.0f}); + } + return w.total_component_count(); + }); + }; + + BENCHMARK_ADVANCED("Create entities with components [5000]")( + Catch::Benchmark::Chronometer meter) + { + meter.measure( + [&] + { + world w; + for(int i = 0; i < MEDIUM; ++i) + { + auto e = w.make_entity(); + w.set(e, {1.0f, 2.0f}); + w.set(e, {3.0f, 4.0f}); + } + return w.total_component_count(); + }); + }; + + BENCHMARK_ADVANCED("Create entities with components [10000]")( + Catch::Benchmark::Chronometer meter) + { + meter.measure( + [&] + { + world w; + for(int i = 0; i < LARGE; ++i) + { + auto e = w.make_entity(); + w.set(e, {1.0f, 2.0f}); + w.set(e, {3.0f, 4.0f}); + } + return w.total_component_count(); + }); + }; +} + +TEST_CASE("Component operations benchmarks", "[benchmark]") +{ + BENCHMARK_ADVANCED("Add component to existing entities [1000]")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < SMALL; ++i) + { + entities.push_back(w.make_entity()); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.set(e, {100}); + } + return w.component_count(); + }); + }; + + BENCHMARK_ADVANCED("Add component to existing entities [5000]")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < MEDIUM; ++i) + { + entities.push_back(w.make_entity()); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.set(e, {100}); + } + return w.component_count(); + }); + }; + + BENCHMARK_ADVANCED("Add component to existing entities [10000]")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < LARGE; ++i) + { + entities.push_back(w.make_entity()); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.set(e, {100}); + } + return w.component_count(); + }); + }; + + BENCHMARK_ADVANCED("Remove component from entities [1000]")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < SMALL; ++i) + { + auto e = w.make_entity(); + w.set(e, {100}); + entities.push_back(e); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.remove(e); + } + return w.component_count(); + }); + }; + + BENCHMARK_ADVANCED("Remove component from entities [5000]")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < MEDIUM; ++i) + { + auto e = w.make_entity(); + w.set(e, {100}); + entities.push_back(e); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.remove(e); + } + return w.component_count(); + }); + }; + + BENCHMARK_ADVANCED("Remove component from entities [10000]")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < LARGE; ++i) + { + auto e = w.make_entity(); + w.set(e, {100}); + entities.push_back(e); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.remove(e); + } + return w.component_count(); + }); + }; +} + +TEST_CASE("Query performance benchmarks", "[benchmark]") +{ + BENCHMARK_ADVANCED("Dense query (90% match)")( + Catch::Benchmark::Chronometer meter) + { + world w; + for(int i = 0; i < LARGE; ++i) + { + auto e = w.make_entity(); + w.set(e); + if(i % 10 != 0) + w.set(e); // 90% match + } + + meter.measure( + [&] + { + int count = 0; + w.query([&](auto...) { count++; }); + return count; + }); + }; + + BENCHMARK_ADVANCED("Sparse query (10% match)")( + Catch::Benchmark::Chronometer meter) + { + world w; + for(int i = 0; i < LARGE; ++i) + { + auto e = w.make_entity(); + w.set(e); + if(i % 10 == 0) + w.set(e); // 10% match + } + + meter.measure( + [&] + { + int count = 0; + w.query([&](auto...) { count++; }); + return count; + }); + }; +} + +TEST_CASE("Memory intensive benchmarks", "[benchmark]") +{ + BENCHMARK_ADVANCED("Large component allocation")( + Catch::Benchmark::Chronometer meter) + { + meter.measure( + [&] + { + world w; + for(int i = 0; i < SMALL; ++i) + { + auto e = w.make_entity(); + w.set(e); + } + return w.total_component_count(); + }); + }; + + BENCHMARK_ADVANCED("Component memory reuse")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < SMALL; ++i) + { + auto e = w.make_entity(); + w.set(e); + entities.push_back(e); + } + + meter.measure( + [&] + { + // Remove and re-add components + for(auto e: entities) + { + w.remove(e); + w.set(e); + } + return w.component_count(); + }); + }; +} + +TEST_CASE("Archetype transition benchmarks", "[benchmark]") +{ + BENCHMARK_ADVANCED("Single component addition")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < MEDIUM; ++i) + { + entities.push_back(w.make_entity()); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.set(e); + } + return w.archetype_count(); + }); + }; + + BENCHMARK_ADVANCED("Multi-component transition")( + Catch::Benchmark::Chronometer meter) + { + world w; + std::vector entities; + for(int i = 0; i < MEDIUM; ++i) + { + auto e = w.make_entity(); + w.set(e); + entities.push_back(e); + } + + meter.measure( + [&] + { + for(auto e: entities) + { + w.set(e); + w.set(e); + } + return w.archetype_count(); + }); + }; +}