From 2c2bc4a4273e91cd6c502e273be9192e42256c9c Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 13 Dec 2023 21:43:11 +0100 Subject: [PATCH] Try to benchmark common MapBlock usage --- src/benchmark/CMakeLists.txt | 1 + src/benchmark/benchmark_mapblock.cpp | 185 +++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) create mode 100644 src/benchmark/benchmark_mapblock.cpp diff --git a/src/benchmark/CMakeLists.txt b/src/benchmark/CMakeLists.txt index 787bbcee4..402c81cbf 100644 --- a/src/benchmark/CMakeLists.txt +++ b/src/benchmark/CMakeLists.txt @@ -2,6 +2,7 @@ set (BENCHMARK_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/benchmark.cpp ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_lighting.cpp ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_serialize.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/benchmark_mapblock.cpp PARENT_SCOPE) set (BENCHMARK_CLIENT_SRCS diff --git a/src/benchmark/benchmark_mapblock.cpp b/src/benchmark/benchmark_mapblock.cpp new file mode 100644 index 000000000..c273764dc --- /dev/null +++ b/src/benchmark/benchmark_mapblock.cpp @@ -0,0 +1,185 @@ +/* +Minetest +Copyright (C) 2023 Minetest Authors + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "benchmark_setup.h" +#include "mapblock.h" +#include + +typedef std::vector MBContainer; + +static void allocateSome(MBContainer &vec, u32 n) +{ + vec.reserve(vec.size() + n); + Map *map = reinterpret_cast(0x1234); + for (u32 i = 0; i < n; i++) { + auto *mb = new MapBlock(map, {i & 0xff, 0, i >> 8}, nullptr); + vec.push_back(mb); + } +} + +static void freeSome(MBContainer &vec, u32 n) +{ + // deallocate from end since that has no cost moving data inside the vector + u32 start_i = 0; + if (vec.size() > n) + start_i = vec.size() - n; + for (u32 i = start_i; i < vec.size(); i++) + delete vec[i]; + vec.resize(start_i); +} + +static inline void freeAll(MBContainer &vec) { freeSome(vec, vec.size()); } + +// usage patterns inspired by ClientMap::updateDrawList() +static void workOnMetadata(const MBContainer &vec) +{ + for (MapBlock *block : vec) { +#ifndef SERVER + bool foo = !!block->mesh; +#else + bool foo = true; +#endif + + if (block->refGet() > 2) + block->refDrop(); + + v3s16 pos = block->getPos() * MAP_BLOCKSIZE; + if (foo) + pos += v3s16(MAP_BLOCKSIZE / 2); + + if (pos.getDistanceFrom(v3s16(0)) > 30000) + continue; + + block->resetUsageTimer(); + block->refGrab(); + } +} + +// usage patterns inspired by LBMManager::applyLBMs() +static u32 workOnNodes(const MBContainer &vec) +{ + u32 foo = 0; + for (MapBlock *block : vec) { + block->resetUsageTimer(); + + if (block->isOrphan()) + continue; + + v3s16 pos_of_block = block->getPosRelative(); + v3s16 pos; + MapNode n; + for (pos.X = 0; pos.X < MAP_BLOCKSIZE; pos.X++) { + for (pos.Y = 0; pos.Y < MAP_BLOCKSIZE; pos.Y++) { + for (pos.Z = 0; pos.Z < MAP_BLOCKSIZE; pos.Z++) { + n = block->getNodeNoCheck(pos); + + if (n.getContent() == CONTENT_AIR) { + auto p = pos + pos_of_block; + foo ^= p.X + p.Y + p.Z; + } + } + } + } + } + return foo; +} + +// usage patterns inspired by ABMHandler::apply() +// touches both metadata and node data at the same time +static u32 workOnBoth(const MBContainer &vec) +{ + int foo = 0; + for (MapBlock *block : vec) { + block->contents.clear(); + block->contents_cached = false; + + v3s16 p0; + for(p0.X=0; p0.XgetNodeNoCheck(p0); + content_t c = n.getContent(); + + if (!block->contents_cached && !block->do_not_cache_contents) { + block->contents.insert(c); + if (block->contents.size() > 10) { + // Too many different nodes... don't try to cache + block->do_not_cache_contents = true; + block->contents.clear(); + } + } + } + block->contents_cached = !block->do_not_cache_contents; + foo += block->contents.size(); + } + return foo; +} + +#define BENCH1(_count) \ + BENCHMARK_ADVANCED("allocate_" #_count)(Catch::Benchmark::Chronometer meter) { \ + MBContainer vec; \ + const u32 pcount = _count / meter.runs(); \ + meter.measure([&] { \ + allocateSome(vec, pcount); \ + return vec.size(); \ + }); \ + freeAll(vec); \ + }; \ + BENCHMARK_ADVANCED("testCase1_" #_count)(Catch::Benchmark::Chronometer meter) { \ + MBContainer vec; \ + allocateSome(vec, _count); \ + meter.measure([&] { \ + workOnMetadata(vec); \ + }); \ + freeAll(vec); \ + }; \ + BENCHMARK_ADVANCED("testCase2_" #_count)(Catch::Benchmark::Chronometer meter) { \ + MBContainer vec; \ + allocateSome(vec, _count); \ + meter.measure([&] { \ + return workOnNodes(vec); \ + }); \ + freeAll(vec); \ + }; \ + BENCHMARK_ADVANCED("testCase3_" #_count)(Catch::Benchmark::Chronometer meter) { \ + MBContainer vec; \ + allocateSome(vec, _count); \ + meter.measure([&] { \ + return workOnBoth(vec); \ + }); \ + freeAll(vec); \ + }; \ + BENCHMARK_ADVANCED("free_" #_count)(Catch::Benchmark::Chronometer meter) { \ + MBContainer vec; \ + allocateSome(vec, _count); \ + /* catch2 does multiple runs so we have to be careful to not dealloc too many */ \ + const u32 pcount = _count / meter.runs(); \ + meter.measure([&] { \ + freeSome(vec, pcount); \ + return vec.size(); \ + }); \ + freeAll(vec); \ + }; + +TEST_CASE("benchmark_mapblock") { + BENCH1(900) + BENCH1(2200) + BENCH1(7500) // <- default client_mapblock_limit +}