SurfaceDataBenchmarks.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #ifdef HAVE_BENCHMARK
  9. #include <AzTest/AzTest.h>
  10. #include <AzCore/Component/Entity.h>
  11. #include <AzCore/Debug/Profiler.h>
  12. #include <AzCore/Memory/PoolAllocator.h>
  13. #include <AzCore/Math/Random.h>
  14. #include <AzCore/Math/Vector2.h>
  15. #include <AzCore/std/containers/array.h>
  16. #include <AzCore/std/containers/span.h>
  17. #include <AzCore/UnitTest/TestTypes.h>
  18. #include <AzFramework/Components/TransformComponent.h>
  19. #include <LmbrCentral/Shape/BoxShapeComponentBus.h>
  20. #include <LmbrCentral/Shape/CylinderShapeComponentBus.h>
  21. #include <SurfaceData/Components/SurfaceDataSystemComponent.h>
  22. #include <SurfaceData/Components/SurfaceDataShapeComponent.h>
  23. namespace UnitTest
  24. {
  25. class SurfaceDataBenchmark : public ::benchmark::Fixture
  26. {
  27. public:
  28. void internalSetUp()
  29. {
  30. m_surfaceDataSystemEntity = AZStd::make_unique<AZ::Entity>();
  31. m_surfaceDataSystemEntity->CreateComponent<SurfaceData::SurfaceDataSystemComponent>();
  32. m_surfaceDataSystemEntity->Init();
  33. m_surfaceDataSystemEntity->Activate();
  34. }
  35. void internalTearDown()
  36. {
  37. m_surfaceDataSystemEntity.reset();
  38. }
  39. // Create an entity with a Transform component and a SurfaceDataShape component at the given position with the given tags.
  40. AZStd::unique_ptr<AZ::Entity> CreateBenchmarkEntity(
  41. AZ::Vector3 worldPos, AZStd::span<const char* const> providerTags, AZStd::span<const char* const> modifierTags)
  42. {
  43. AZStd::unique_ptr<AZ::Entity> entity = AZStd::make_unique<AZ::Entity>();
  44. auto transform = entity->CreateComponent<AzFramework::TransformComponent>();
  45. transform->SetWorldTM(AZ::Transform::CreateTranslation(worldPos));
  46. SurfaceData::SurfaceDataShapeConfig surfaceConfig;
  47. for (auto& providerTag : providerTags)
  48. {
  49. surfaceConfig.m_providerTags.push_back(SurfaceData::SurfaceTag(providerTag));
  50. }
  51. for (auto& modifierTag : modifierTags)
  52. {
  53. surfaceConfig.m_modifierTags.push_back(SurfaceData::SurfaceTag(modifierTag));
  54. }
  55. entity->CreateComponent<SurfaceData::SurfaceDataShapeComponent>(surfaceConfig);
  56. return entity;
  57. }
  58. /* Create a set of shape surfaces in the world that we can use for benchmarking.
  59. Each shape is centered in XY and is the XY size of the world, but with different Z heights and placements.
  60. There are two boxes and one cylinder, layered like this:
  61. Top:
  62. ---
  63. |O| <- two boxes of equal XY size with a cylinder face-up in the center
  64. ---
  65. Side:
  66. |-----------|
  67. | |<- entity 3, box that contains the other shapes
  68. | |-------| | <- entity 2, cylinder inside entity 3 and intersecting entity 1
  69. | | | |
  70. |-----------|<- entity 1, thin box
  71. | |-------| |
  72. | |
  73. |-----------|
  74. This will give us either 2 or 3 generated surface points at every query point. The entity 1 surface will get the entity 2 and 3
  75. modifier tags added to it. The entity 2 surface will get the entity 3 modifier tag added to it. The entity 3 surface won't get
  76. modified.
  77. */
  78. AZStd::vector<AZStd::unique_ptr<AZ::Entity>> CreateBenchmarkEntities(float worldSize)
  79. {
  80. AZStd::vector<AZStd::unique_ptr<AZ::Entity>> testEntities;
  81. float halfWorldSize = worldSize / 2.0f;
  82. // Create a large flat box with 1 provider tag.
  83. AZStd::unique_ptr<AZ::Entity> surface1 = CreateBenchmarkEntity(
  84. AZ::Vector3(halfWorldSize, halfWorldSize, 10.0f), AZStd::array{ "surface1" }, {});
  85. {
  86. LmbrCentral::BoxShapeConfig boxConfig(AZ::Vector3(worldSize, worldSize, 1.0f));
  87. auto shapeComponent = surface1->CreateComponent(LmbrCentral::BoxShapeComponentTypeId);
  88. shapeComponent->SetConfiguration(boxConfig);
  89. surface1->Init();
  90. surface1->Activate();
  91. }
  92. testEntities.push_back(AZStd::move(surface1));
  93. // Create a large cylinder with 1 provider tag and 1 modifier tag.
  94. AZStd::unique_ptr<AZ::Entity> surface2 = CreateBenchmarkEntity(
  95. AZ::Vector3(halfWorldSize, halfWorldSize, 20.0f), AZStd::array{ "surface2" }, AZStd::array{ "modifier2" });
  96. {
  97. LmbrCentral::CylinderShapeConfig cylinderConfig;
  98. cylinderConfig.m_height = 30.0f;
  99. cylinderConfig.m_radius = halfWorldSize;
  100. auto shapeComponent = surface2->CreateComponent(LmbrCentral::CylinderShapeComponentTypeId);
  101. shapeComponent->SetConfiguration(cylinderConfig);
  102. surface2->Init();
  103. surface2->Activate();
  104. }
  105. testEntities.push_back(AZStd::move(surface2));
  106. // Create a large box with 1 provider tag and 1 modifier tag.
  107. AZStd::unique_ptr<AZ::Entity> surface3 = CreateBenchmarkEntity(
  108. AZ::Vector3(halfWorldSize, halfWorldSize, 30.0f), AZStd::array{ "surface3" }, AZStd::array{ "modifier3" });
  109. {
  110. LmbrCentral::BoxShapeConfig boxConfig(AZ::Vector3(worldSize, worldSize, 100.0f));
  111. auto shapeComponent = surface3->CreateComponent(LmbrCentral::BoxShapeComponentTypeId);
  112. shapeComponent->SetConfiguration(boxConfig);
  113. surface3->Init();
  114. surface3->Activate();
  115. }
  116. testEntities.push_back(AZStd::move(surface3));
  117. return testEntities;
  118. }
  119. SurfaceData::SurfaceTagVector CreateBenchmarkTagFilterList()
  120. {
  121. SurfaceData::SurfaceTagVector tagFilterList;
  122. tagFilterList.emplace_back("surface1");
  123. tagFilterList.emplace_back("surface2");
  124. tagFilterList.emplace_back("surface3");
  125. tagFilterList.emplace_back("modifier2");
  126. tagFilterList.emplace_back("modifier3");
  127. return tagFilterList;
  128. }
  129. protected:
  130. void SetUp([[maybe_unused]] const benchmark::State& state) override
  131. {
  132. internalSetUp();
  133. }
  134. void SetUp([[maybe_unused]] benchmark::State& state) override
  135. {
  136. internalSetUp();
  137. }
  138. void TearDown([[maybe_unused]] const benchmark::State& state) override
  139. {
  140. internalTearDown();
  141. }
  142. void TearDown([[maybe_unused]] benchmark::State& state) override
  143. {
  144. internalTearDown();
  145. }
  146. AZStd::unique_ptr<AZ::Entity> m_surfaceDataSystemEntity;
  147. };
  148. BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePoints)(benchmark::State& state)
  149. {
  150. AZ_PROFILE_FUNCTION(Entity);
  151. // Create our benchmark world
  152. const float worldSize = aznumeric_cast<float>(state.range(0));
  153. AZStd::vector<AZStd::unique_ptr<AZ::Entity>> benchmarkEntities = CreateBenchmarkEntities(worldSize);
  154. SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList();
  155. // Query every point in our world at 1 meter intervals.
  156. for ([[maybe_unused]] auto _ : state)
  157. {
  158. // This is declared outside the loop so that the list of points doesn't fully reallocate on every query.
  159. SurfaceData::SurfacePointList points;
  160. for (float y = 0.0f; y < worldSize; y += 1.0f)
  161. {
  162. for (float x = 0.0f; x < worldSize; x += 1.0f)
  163. {
  164. AZ::Vector3 queryPosition(x, y, 0.0f);
  165. points.Clear();
  166. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePoints(queryPosition, filterTags, points);
  167. benchmark::DoNotOptimize(points);
  168. }
  169. }
  170. }
  171. }
  172. BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromRegion)(benchmark::State& state)
  173. {
  174. AZ_PROFILE_FUNCTION(Entity);
  175. // Create our benchmark world
  176. float worldSize = aznumeric_cast<float>(state.range(0));
  177. AZStd::vector<AZStd::unique_ptr<AZ::Entity>> benchmarkEntities = CreateBenchmarkEntities(worldSize);
  178. SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList();
  179. // Query every point in our world at 1 meter intervals.
  180. for ([[maybe_unused]] auto _ : state)
  181. {
  182. SurfaceData::SurfacePointList points;
  183. AZ::Aabb inRegion = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(worldSize));
  184. AZ::Vector2 stepSize(1.0f);
  185. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromRegion(inRegion, stepSize, filterTags, points);
  186. benchmark::DoNotOptimize(points);
  187. }
  188. }
  189. BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromList)(benchmark::State& state)
  190. {
  191. AZ_PROFILE_FUNCTION(Entity);
  192. // Create our benchmark world
  193. const float worldSize = aznumeric_cast<float>(state.range(0));
  194. const int64_t worldSizeInt = state.range(0);
  195. AZStd::vector<AZStd::unique_ptr<AZ::Entity>> benchmarkEntities = CreateBenchmarkEntities(worldSize);
  196. SurfaceData::SurfaceTagVector filterTags = CreateBenchmarkTagFilterList();
  197. // Query every point in our world at 1 meter intervals.
  198. for ([[maybe_unused]] auto _ : state)
  199. {
  200. AZStd::vector<AZ::Vector3> queryPositions;
  201. queryPositions.reserve(worldSizeInt * worldSizeInt);
  202. for (float y = 0.0f; y < worldSize; y += 1.0f)
  203. {
  204. for (float x = 0.0f; x < worldSize; x += 1.0f)
  205. {
  206. queryPositions.emplace_back(x, y, 0.0f);
  207. }
  208. }
  209. SurfaceData::SurfacePointList points;
  210. AZ::Interface<SurfaceData::SurfaceDataSystem>::Get()->GetSurfacePointsFromList(queryPositions, filterTags, points);
  211. benchmark::DoNotOptimize(points);
  212. }
  213. }
  214. BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePoints)
  215. ->Arg( 1024 )
  216. ->Arg( 2048 )
  217. ->Unit(::benchmark::kMillisecond);
  218. BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromRegion)
  219. ->Arg( 1024 )
  220. ->Arg( 2048 )
  221. ->Unit(::benchmark::kMillisecond);
  222. BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_GetSurfacePointsFromList)
  223. ->Arg( 1024 )
  224. ->Arg( 2048 )
  225. ->Unit(::benchmark::kMillisecond);
  226. BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_AddSurfaceTagWeight)(benchmark::State& state)
  227. {
  228. AZ_PROFILE_FUNCTION(Entity);
  229. AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights];
  230. AZ::SimpleLcgRandom randomGenerator(1234567);
  231. // Declare this outside the loop so that we aren't benchmarking creation and destruction.
  232. SurfaceData::SurfaceTagWeights weights;
  233. bool clearEachTime = state.range(0) > 0;
  234. // Create a list of randomly-generated tag values.
  235. for (auto& tag : tags)
  236. {
  237. tag = randomGenerator.GetRandom();
  238. }
  239. for ([[maybe_unused]] auto _ : state)
  240. {
  241. // We'll benchmark this two ways:
  242. // 1. We clear each time, which means each AddSurfaceWeightIfGreater call will search the whole list then add.
  243. // 2. We don't clear, which means that after the first run, AddSurfaceWeightIfGreater will always try to replace values.
  244. if (clearEachTime)
  245. {
  246. weights.Clear();
  247. }
  248. // For each tag, try to add it with a random weight.
  249. for (auto& tag : tags)
  250. {
  251. weights.AddSurfaceTagWeight(tag, randomGenerator.GetRandomFloat());
  252. }
  253. }
  254. }
  255. BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_AddSurfaceTagWeight)
  256. ->Arg(false)
  257. ->Arg(true)
  258. ->ArgName("ClearEachTime");
  259. BENCHMARK_DEFINE_F(SurfaceDataBenchmark, BM_HasAnyMatchingTags_NoMatches)(benchmark::State& state)
  260. {
  261. AZ_PROFILE_FUNCTION(Entity);
  262. AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights];
  263. AZ::SimpleLcgRandom randomGenerator(1234567);
  264. // Declare this outside the loop so that we aren't benchmarking creation and destruction.
  265. SurfaceData::SurfaceTagWeights weights;
  266. // Create a list of randomly-generated tag values.
  267. for (auto& tag : tags)
  268. {
  269. // Specifically always set the last bit so that we can create comparison tags that won't match.
  270. tag = randomGenerator.GetRandom() | 0x01;
  271. // Add the tag to our weights list with a random weight.
  272. weights.AddSurfaceTagWeight(tag, randomGenerator.GetRandomFloat());
  273. }
  274. // Create a set of similar comparison tags that won't match. We still want a random distribution of values though,
  275. // because the SurfaceTagWeights might behave differently with ordered lists.
  276. SurfaceData::SurfaceTagVector comparisonTags;
  277. for (auto& tag : tags)
  278. {
  279. comparisonTags.emplace_back(tag ^ 0x01);
  280. }
  281. for ([[maybe_unused]] auto _ : state)
  282. {
  283. // Test to see if any of our tags match.
  284. // All of comparison tags should get compared against all of the added tags.
  285. bool result = weights.HasAnyMatchingTags(comparisonTags);
  286. benchmark::DoNotOptimize(result);
  287. }
  288. }
  289. BENCHMARK_REGISTER_F(SurfaceDataBenchmark, BM_HasAnyMatchingTags_NoMatches);
  290. #endif
  291. }