From c330c92160fce2816127fd9b5e0516daae2bf795 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Fri, 20 Nov 2020 17:00:00 -0800 Subject: [PATCH 01/10] Add experimental memory tools based on Mimick More testing is needed before we switch over to this new memory recording approach, but I added a test demonstrating how to use it right now. The Benchmark MemoryManager system exposes the metrics under different names, and unfortunately, only the JSON reporter is currently writing them out. Signed-off-by: Scott K Logan --- CMakeLists.txt | 48 +++- include/performance_test_fixture/common.hpp | 37 ++++ .../mimick_memory_manager.hpp | 77 +++++++ .../mimick_performance_test_fixture.hpp | 47 ++++ .../performance_test_fixture.hpp | 26 +-- package.xml | 1 + src/mimick_memory_manager.cpp | 207 ++++++++++++++++++ src/mimick_performance_test_fixture.cpp | 77 +++++++ src/mimick_utils.hpp | 87 ++++++++ src/performance_test_fixture.cpp | 4 +- .../benchmark_malloc_realloc_mimick.cpp | 145 ++++++++++++ 11 files changed, 730 insertions(+), 26 deletions(-) create mode 100644 include/performance_test_fixture/common.hpp create mode 100644 include/performance_test_fixture/mimick_memory_manager.hpp create mode 100644 include/performance_test_fixture/mimick_performance_test_fixture.hpp create mode 100644 src/mimick_memory_manager.cpp create mode 100644 src/mimick_performance_test_fixture.cpp create mode 100644 src/mimick_utils.hpp create mode 100644 test/benchmark/benchmark_malloc_realloc_mimick.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 25dba47..5d77cb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ find_package(ament_cmake_export_dependencies REQUIRED) find_package(ament_cmake_export_targets REQUIRED) find_package(ament_cmake_test REQUIRED) find_package(benchmark REQUIRED) +find_package(mimick_vendor REQUIRED) find_package(osrf_testing_tools_cpp REQUIRED) add_library(${PROJECT_NAME} @@ -34,7 +35,38 @@ target_link_libraries(${PROJECT_NAME} PUBLIC target_compile_definitions(${PROJECT_NAME} PRIVATE "PERFORMANCE_TEST_FIXTURE_BUILDING_DLL") -install(TARGETS ${PROJECT_NAME} +# TODO(cottsay): It would be great if this didn't need to be STATIC, but +# libmimick is specifically compiled static and without PIC. +add_library(mimick_memory_manager STATIC + src/mimick_memory_manager.cpp) + +target_include_directories(mimick_memory_manager PUBLIC + $ + $) + +target_include_directories(mimick_memory_manager SYSTEM PRIVATE + $ + $) + +target_link_libraries(mimick_memory_manager INTERFACE + benchmark::benchmark + mimick) + +add_library(mimick_${PROJECT_NAME} STATIC + src/mimick_${PROJECT_NAME}.cpp) + +target_include_directories(mimick_${PROJECT_NAME} PUBLIC + $ + $) + +target_include_directories(mimick_${PROJECT_NAME} SYSTEM PRIVATE + $) + +target_link_libraries(mimick_${PROJECT_NAME} INTERFACE + benchmark::benchmark + mimick_memory_manager) + +install(TARGETS ${PROJECT_NAME} mimick_memory_manager mimick_${PROJECT_NAME} EXPORT ${PROJECT_NAME} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib @@ -50,7 +82,10 @@ install( ament_export_targets(${PROJECT_NAME}) ament_export_dependencies( - ament_cmake_google_benchmark benchmark osrf_testing_tools_cpp) + ament_cmake_google_benchmark + benchmark + mimick_vendor + osrf_testing_tools_cpp) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) @@ -84,6 +119,15 @@ if(BUILD_TESTING) add_performance_test( benchmark_pause_resume test/benchmark/benchmark_pause_resume.cpp) + + ament_add_google_benchmark( + benchmark_malloc_realloc_mimick + test/benchmark/benchmark_malloc_realloc_mimick.cpp + TIMEOUT 60) + if(TARGET benchmark_malloc_realloc_mimick) + target_link_libraries(benchmark_malloc_realloc_mimick + mimick_performance_test_fixture) + endif() endif() ament_package( diff --git a/include/performance_test_fixture/common.hpp b/include/performance_test_fixture/common.hpp new file mode 100644 index 0000000..a7f2519 --- /dev/null +++ b/include/performance_test_fixture/common.hpp @@ -0,0 +1,37 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PERFORMANCE_TEST_FIXTURE__COMMON_HPP_ +#define PERFORMANCE_TEST_FIXTURE__COMMON_HPP_ + +/** + * Macro to pause timing and heap allocation measurements over a section of code. + * + * This is useful if there is setup or teardown that has to occur with every iteration. + * For example, if you wanted to measure only construction of a rclcpp::Node and not its + * destruction. + * + * state.PauseTiming() does not allocate, so it should go first to minimize discrepencies in + * measuring timing + */ +#define PERFORMANCE_TEST_FIXTURE_PAUSE_MEASUREMENTS(state, code) \ + do { \ + state.PauseTiming(); \ + set_are_allocation_measurements_active(false); \ + code; \ + set_are_allocation_measurements_active(true); \ + state.ResumeTiming(); \ + } while (0) + +#endif // PERFORMANCE_TEST_FIXTURE__COMMON_HPP_ diff --git a/include/performance_test_fixture/mimick_memory_manager.hpp b/include/performance_test_fixture/mimick_memory_manager.hpp new file mode 100644 index 0000000..67c9b8f --- /dev/null +++ b/include/performance_test_fixture/mimick_memory_manager.hpp @@ -0,0 +1,77 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PERFORMANCE_TEST_FIXTURE__MIMICK_MEMORY_MANAGER_HPP_ +#define PERFORMANCE_TEST_FIXTURE__MIMICK_MEMORY_MANAGER_HPP_ + +#include + +#include +#include +#include +#include +#include + +// Defined by mimick.h +struct mmk_stub; + +// Defined by mimick_utils.hpp +template +struct mmk_allocator; + +namespace performance_test_fixture +{ + +class MimickMemoryManager : public benchmark::MemoryManager +{ +public: + MimickMemoryManager(); + + ~MimickMemoryManager(); + + virtual void Pause(); + + virtual void Reset(); + + virtual void Resume(); + + void Start() override; + + void Stop(benchmark::MemoryManager::Result * result) override; + +private: + void Stop(); + + void on_free(void * ptr); + void * on_malloc(size_t size); + void * on_realloc(void * ptr, size_t size); + + std::mutex stat_lock; + bool recording_enabled; + + decltype(benchmark::MemoryManager::Result::max_bytes_used) cur_bytes_used; + decltype(benchmark::MemoryManager::Result::max_bytes_used) max_bytes_used; + decltype(benchmark::MemoryManager::Result::num_allocs) num_allocs; + std::unique_ptr< + std::map, mmk_allocator::value_type>>> ptr_map; + + struct mmk_stub * free_stub; + struct mmk_stub * malloc_stub; + struct mmk_stub * realloc_stub; +}; + +} // namespace performance_test_fixture + +#endif // PERFORMANCE_TEST_FIXTURE__MIMICK_MEMORY_MANAGER_HPP_ diff --git a/include/performance_test_fixture/mimick_performance_test_fixture.hpp b/include/performance_test_fixture/mimick_performance_test_fixture.hpp new file mode 100644 index 0000000..199182f --- /dev/null +++ b/include/performance_test_fixture/mimick_performance_test_fixture.hpp @@ -0,0 +1,47 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PERFORMANCE_TEST_FIXTURE__MIMICK_PERFORMANCE_TEST_FIXTURE_HPP_ +#define PERFORMANCE_TEST_FIXTURE__MIMICK_PERFORMANCE_TEST_FIXTURE_HPP_ + +#include + +#include + +#include "performance_test_fixture/common.hpp" +#include "performance_test_fixture/mimick_memory_manager.hpp" + +namespace performance_test_fixture +{ + +class MimickPerformanceTest : public benchmark::Fixture +{ +public: + MimickPerformanceTest(); + + void SetUp(benchmark::State & state) override; + + void TearDown(benchmark::State & state) override; + +protected: + void reset_heap_counters(); + + void set_are_allocation_measurements_active(bool value); + + std::unique_ptr memory_manager; +}; + +} // namespace performance_test_fixture + +#endif // PERFORMANCE_TEST_FIXTURE__MIMICK_PERFORMANCE_TEST_FIXTURE_HPP_ diff --git a/include/performance_test_fixture/performance_test_fixture.hpp b/include/performance_test_fixture/performance_test_fixture.hpp index 9a42515..95ea771 100644 --- a/include/performance_test_fixture/performance_test_fixture.hpp +++ b/include/performance_test_fixture/performance_test_fixture.hpp @@ -20,22 +20,23 @@ #include +#include "performance_test_fixture/common.hpp" #include "performance_test_fixture/visibility_control.hpp" namespace performance_test_fixture { -class PerformanceTest : public ::benchmark::Fixture +class PerformanceTest : public benchmark::Fixture { public: PERFORMANCE_TEST_FIXTURE_PUBLIC PerformanceTest(); PERFORMANCE_TEST_FIXTURE_PUBLIC - void SetUp(::benchmark::State & state); + void SetUp(benchmark::State & state) override; PERFORMANCE_TEST_FIXTURE_PUBLIC - void TearDown(::benchmark::State & state); + void TearDown(benchmark::State & state) override; protected: PERFORMANCE_TEST_FIXTURE_PUBLIC @@ -58,23 +59,4 @@ class PerformanceTest : public ::benchmark::Fixture } // namespace performance_test_fixture -/** - * Macro to pause timing and heap allocation measurements over a section of code. - * - * This is useful if there is setup or teardown that has to occur with every iteration. - * For example, if you wanted to measure only construction of a rclcpp::Node and not its - * destruction. - * - * state.PauseTiming() does not allocate, so it should go first to minimize discrepencies in - * measuring timing - */ -#define PERFORMANCE_TEST_FIXTURE_PAUSE_MEASUREMENTS(state, code) \ - do { \ - state.PauseTiming(); \ - set_are_allocation_measurements_active(false); \ - code; \ - set_are_allocation_measurements_active(true); \ - state.ResumeTiming(); \ - } while (0) - #endif // PERFORMANCE_TEST_FIXTURE__PERFORMANCE_TEST_FIXTURE_HPP_ diff --git a/package.xml b/package.xml index 5d9f265..904eb5b 100644 --- a/package.xml +++ b/package.xml @@ -19,6 +19,7 @@ ament_cmake_google_benchmark google_benchmark_vendor + mimick_vendor osrf_testing_tools_cpp ament_cmake_google_benchmark diff --git a/src/mimick_memory_manager.cpp b/src/mimick_memory_manager.cpp new file mode 100644 index 0000000..b22f953 --- /dev/null +++ b/src/mimick_memory_manager.cpp @@ -0,0 +1,207 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "performance_test_fixture/mimick_memory_manager.hpp" + +#include +#include +#include +#include + +#include "mimick/mimick.h" +#include "./mimick_utils.hpp" + +namespace performance_test_fixture +{ + +MimickMemoryManager::MimickMemoryManager() +: recording_enabled(true), + cur_bytes_used(0), + max_bytes_used(0), + num_allocs(0), + ptr_map( + new std::map, mmk_allocator::value_type>>()), + free_stub(MMK_STUB_INVALID), + malloc_stub(MMK_STUB_INVALID), + realloc_stub(MMK_STUB_INVALID) +{ +} + +MimickMemoryManager::~MimickMemoryManager() +{ + Stop(); +} + +void MimickMemoryManager::Pause() +{ + stat_lock.lock(); + + recording_enabled = false; + + stat_lock.unlock(); +} + +void MimickMemoryManager::Reset() +{ + stat_lock.lock(); + + cur_bytes_used = 0; + max_bytes_used = 0; + num_allocs = 0; + ptr_map->clear(); + + stat_lock.unlock(); +} + +void MimickMemoryManager::Resume() +{ + stat_lock.lock(); + + recording_enabled = true; + + stat_lock.unlock(); +} + +void MimickMemoryManager::Start() +{ + stat_lock.lock(); + + Stop(); + + recording_enabled = true; + + cur_bytes_used = 0; + max_bytes_used = 0; + num_allocs = 0; + ptr_map->clear(); + + free_stub = mmk_stub_create_wrapped("free", on_free, this, void *); + if (MMK_STUB_INVALID == free_stub) { + std::cerr << "Failed to create 'free' stub!" << std::endl; + exit(1); + } + + malloc_stub = mmk_stub_create_wrapped("malloc", on_malloc, this, size_t); + if (MMK_STUB_INVALID == malloc_stub) { + std::cerr << "Failed to create 'malloc' stub!" << std::endl; + exit(1); + } + + realloc_stub = mmk_stub_create_wrapped("realloc", on_realloc, this, void *, size_t); + if (MMK_STUB_INVALID == realloc_stub) { + std::cerr << "Failed to create 'realloc' stub!" << std::endl; + exit(1); + } + + stat_lock.unlock(); +} + +void MimickMemoryManager::Stop(benchmark::MemoryManager::Result * result) +{ + stat_lock.lock(); + + Stop(); + + if (nullptr != result) { + result->max_bytes_used = max_bytes_used; + result->num_allocs = num_allocs; + } + + stat_lock.unlock(); +} + +void MimickMemoryManager::Stop() +{ + if (MMK_STUB_INVALID != free_stub) { + mmk_stub_destroy(free_stub); + free_stub = MMK_STUB_INVALID; + } + + if (MMK_STUB_INVALID != malloc_stub) { + mmk_stub_destroy(malloc_stub); + malloc_stub = MMK_STUB_INVALID; + } + + if (MMK_STUB_INVALID != realloc_stub) { + mmk_stub_destroy(realloc_stub); + realloc_stub = MMK_STUB_INVALID; + } +} + +void MimickMemoryManager::on_free(void * ptr) +{ + stat_lock.lock(); + mmk_free(ptr); + + auto ph = ptr_map->find(ptr); + if (ph != ptr_map->end()) { + cur_bytes_used -= ph->second; + ptr_map->erase(ph); + } + + stat_lock.unlock(); +} + +void * MimickMemoryManager::on_malloc(size_t size) +{ + stat_lock.lock(); + void * new_ptr = mmk_malloc(size); + + if (recording_enabled) { + num_allocs++; + if (nullptr != new_ptr) { + (*ptr_map)[new_ptr] = size; + cur_bytes_used += size; + if (cur_bytes_used > max_bytes_used) { + max_bytes_used = cur_bytes_used; + } + } + } + + stat_lock.unlock(); + + return new_ptr; +} + +void * MimickMemoryManager::on_realloc(void * ptr, size_t size) +{ + stat_lock.lock(); + void * new_ptr = mmk_realloc(ptr, size); + + if (nullptr != new_ptr || 0 == size) { + auto ph = ptr_map->find(ptr); + if (ph != ptr_map->end()) { + cur_bytes_used -= ph->second; + ptr_map->erase(ph); + } + } + + if (recording_enabled) { + num_allocs++; + if (nullptr != new_ptr) { + (*ptr_map)[new_ptr] = size; + cur_bytes_used += size; + if (cur_bytes_used > max_bytes_used) { + max_bytes_used = cur_bytes_used; + } + } + } + + stat_lock.unlock(); + + return new_ptr; +} + +} // namespace performance_test_fixture diff --git a/src/mimick_performance_test_fixture.cpp b/src/mimick_performance_test_fixture.cpp new file mode 100644 index 0000000..98f474d --- /dev/null +++ b/src/mimick_performance_test_fixture.cpp @@ -0,0 +1,77 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "performance_test_fixture/mimick_performance_test_fixture.hpp" + +namespace performance_test_fixture +{ + +/* + * This is a lightweight MemoryManager wrapper that unregisters itself when it + * is asked to stop recording. This is necessary so that when a benchmark + * executable contains multiple tests and not all of them use this fixture, + * the memory manager (which is stored in Google Benchmark as a global) isn't + * registered despite the absence of this fixture. + * + * Since that behavior is specific to our use of a fixture to take care of + * the MemoryManager registration, it isn't intrinsic to the MemoryManager + * concept in general, which is why this class is implemented here. + */ +template +class AutoStopMemoryManager : public U +{ +public: + void Stop(benchmark::MemoryManager::Result * result) override + { + U::Stop(result); + benchmark::RegisterMemoryManager(nullptr); + } +}; + +MimickPerformanceTest::MimickPerformanceTest() +: memory_manager(new AutoStopMemoryManager()) +{ +} + +void MimickPerformanceTest::SetUp(benchmark::State &) +{ + // Registering the MemoryManager doesn't mean that it will be used in the + // current run. Benchmark does the timing measurements first, so this will + // cause the MemoryManager to be registered during those runs, but it won't + // be actually started until the registration is checked after the timing + // runs are finished. + benchmark::RegisterMemoryManager(memory_manager.get()); + memory_manager->Reset(); +} + +void MimickPerformanceTest::TearDown(benchmark::State &) +{ + memory_manager->Pause(); +} + +void MimickPerformanceTest::reset_heap_counters() +{ + memory_manager->Reset(); +} + +void MimickPerformanceTest::set_are_allocation_measurements_active(bool value) +{ + if (value) { + memory_manager->Resume(); + } else { + memory_manager->Pause(); + } +} + +} // namespace performance_test_fixture diff --git a/src/mimick_utils.hpp b/src/mimick_utils.hpp new file mode 100644 index 0000000..98c9968 --- /dev/null +++ b/src/mimick_utils.hpp @@ -0,0 +1,87 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MIMICK_UTILS_HPP_ +#define MIMICK_UTILS_HPP_ + +#include +#include + +#include "mimick/mimick.h" + +template +typename std::result_of::type +mmk_wrapper(A... a) +{ + struct mmk_stub * stub = mmk_ctx(); + assert(nullptr != stub); + C * instance = static_cast(mmk_stub_context(stub)); + assert(nullptr != instance); + return (instance->*fn)(a ...); +} + +#define mmk_stub_create_wrapped(name, func, inst, ...) \ + mmk_stub_create( \ + name, \ + (mmk_fn) & mmk_wrapper< \ + decltype(&std::remove_reference::type::func), \ + &std::remove_reference::type::func, \ + std::remove_reference::type, \ + __VA_ARGS__>, \ + inst); + +template +struct mmk_allocator +{ + typedef T value_type; + + mmk_allocator() + { + // TODO(cottsay): This is just a hack to get Mimick to initialize the "vital" functions. + mmk_stub * stub = mmk_stub_create("free", (mmk_fn)dummy_free, nullptr); + if (MMK_STUB_INVALID == stub) { + abort(); + } + mmk_stub_destroy(stub); + } + + template + constexpr mmk_allocator(const mmk_allocator &) noexcept {} + + T * allocate(std::size_t n) + { + if (n > std::numeric_limits::max() / sizeof(T)) { + throw std::bad_alloc(); + } + + if (T * p = static_cast(mmk_malloc(n * sizeof(T)))) { + return p; + } + + throw std::bad_alloc(); + } + + void deallocate(T * p, std::size_t) noexcept + { + mmk_free(p); + } + +private: + static void dummy_free(void * unused) + { + (void)unused; + } +}; + +#endif // MIMICK_UTILS_HPP_ diff --git a/src/performance_test_fixture.cpp b/src/performance_test_fixture.cpp index 6c34f06..48a8bc5 100644 --- a/src/performance_test_fixture.cpp +++ b/src/performance_test_fixture.cpp @@ -26,7 +26,8 @@ namespace performance_test_fixture { PerformanceTest::PerformanceTest() -: suppress_memory_tools_logging(true), are_allocation_measurements_active(false) +: suppress_memory_tools_logging(true), + are_allocation_measurements_active(false) { const char * performance_test_fixture_enable_trace = getenv( "PERFORMANCE_TEST_FIXTURE_ENABLE_TRACE"); @@ -114,5 +115,4 @@ void PerformanceTest::set_are_allocation_measurements_active(bool value) are_allocation_measurements_active = value; } - } // namespace performance_test_fixture diff --git a/test/benchmark/benchmark_malloc_realloc_mimick.cpp b/test/benchmark/benchmark_malloc_realloc_mimick.cpp new file mode 100644 index 0000000..fe7e195 --- /dev/null +++ b/test/benchmark/benchmark_malloc_realloc_mimick.cpp @@ -0,0 +1,145 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include + +#include "./macros.h" +#include "performance_test_fixture/mimick_performance_test_fixture.hpp" + +namespace +{ + +constexpr int kForcePerformanceTracking = 2; +constexpr int kEnablePerformanceTracking = 1; +constexpr int kDisablePerformanceTracking = 0; + +} // namespace + +class PerformanceTestFixture : public performance_test_fixture::MimickPerformanceTest +{ +public: + void SetUp(benchmark::State & state) + { + switch (state.range(0)) { + case kDisablePerformanceTracking: + break; + case kEnablePerformanceTracking: + performance_test_fixture::MimickPerformanceTest::SetUp(state); + break; + case kForcePerformanceTracking: + performance_test_fixture::MimickPerformanceTest::SetUp(state); + memory_manager->Start(); + break; + default: + std::string message = + std::string("Invalid enable/disable value: ") + std::to_string(state.range(0)); + state.SkipWithError(message.c_str()); + break; + } + } + + void TearDown(benchmark::State & state) + { + if (kEnablePerformanceTracking == state.range(0)) { + performance_test_fixture::MimickPerformanceTest::TearDown(state); + } else if (kForcePerformanceTracking == state.range(0)) { + memory_manager->Pause(); + } + } +}; + +// This does not make use of PauseTiming or ResumeTiming because timing is very short for these +// benchmarks. However, they should be enough to measure the overhead of turning on performance +// measurements. +BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc)( + benchmark::State & state) +{ + const size_t malloc_size = state.range(1); + if (malloc_size < 1) { + state.SkipWithError("Size for allocation is too small for this test"); + } + for (auto _ : state) { + void * ptr = std::malloc(malloc_size); + if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { + state.SkipWithError("Malloc failed to malloc"); + } + std::free(ptr); + benchmark::DoNotOptimize(ptr); + benchmark::ClobberMemory(); + } +} + +BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( + benchmark::State & state) +{ + const int64_t malloc_size = state.range(1); + if (malloc_size < 1) { + state.SkipWithError("Size for allocation is too small for this test"); + } + const size_t realloc_size = state.range(2); + if (realloc_size < 1) { + state.SkipWithError("Size for reallocation is too small for this test"); + } + for (auto _ : state) { + void * ptr = std::malloc(malloc_size); + if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { + state.SkipWithError("Malloc failed to malloc"); + } + void * realloc_ptr = std::realloc(ptr, realloc_size); + if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == realloc_ptr)) { + std::free(ptr); + state.SkipWithError("Malloc failed to realloc"); + } + std::free(realloc_ptr); + benchmark::DoNotOptimize(realloc_ptr); + benchmark::ClobberMemory(); + } +} + +// Malloc sizes Range from 1 to 2^27 each time multiplying by 16. Each value tested +// with/without performance metrics +static void malloc_args(benchmark::internal::Benchmark * b) +{ + for (int64_t shift_left = 0; shift_left < 32; shift_left += 4) { + b->Args({kDisablePerformanceTracking, 1ll << shift_left}); + b->Args({kEnablePerformanceTracking, 1ll << shift_left}); + b->Args({kForcePerformanceTracking, 1ll << shift_left}); + } +} + +BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc) +->ArgNames({"Enable Performance Tracking", "Malloc Size"})->Apply(malloc_args); + +// Three types of realloc tests, one where malloc is smaller than realloc, one where they are +// the same, and one where malloc is larger than realloc. Realloc size ranges from 1 to 2^27 +// each time multiplying by 32. Each stop is tested with/without performance metrics +static void realloc_args(benchmark::internal::Benchmark * b) +{ + for (int64_t malloc_adjustment = -1; malloc_adjustment <= 1; ++malloc_adjustment) { + for (int64_t realloc_shift = 0; realloc_shift < 32; realloc_shift += 8) { + const int64_t malloc_shift = realloc_shift + malloc_adjustment; + if (malloc_shift < 0) { + continue; + } + b->Args({kDisablePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); + b->Args({kEnablePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); + b->Args({kForcePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); + } + } +} + +BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_realloc) +->ArgNames({"Enable Performance Tracking", "Malloc Size", "Realloc Size"}) +->Apply(realloc_args); From b48fb91386fda552ab3199d8005094101358696f Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Fri, 20 Nov 2020 22:26:55 -0800 Subject: [PATCH 02/10] Add a memory-aware Benchmark console reporter The provided console reporter in Benchmark doesn't display memory statistics at all. This change adds two things: 1. A BenchmarkReporter which augments the run data so that the memory statistics are shown as user counters (even though they're not). 2. A modified version of libbenchmark_main.so which utilizes (1) in place of the default console reporter. Note that use of (2) means that the command line arguments that augment the behavior of the console output won't work. The API doesn't provide a mechanism to get those options after they've been parsed. Also note that the JSON reporter does process memory statistics, so no change is necessary there. Signed-off-by: Scott K Logan --- CMakeLists.txt | 22 ++++++++++-- src/memory_aware_benchmark_main.cpp | 36 ++++++++++++++++++++ src/memory_aware_console_reporter.cpp | 49 +++++++++++++++++++++++++++ src/memory_aware_console_reporter.hpp | 35 +++++++++++++++++++ 4 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 src/memory_aware_benchmark_main.cpp create mode 100644 src/memory_aware_console_reporter.cpp create mode 100644 src/memory_aware_console_reporter.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d77cb1..4330711 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,21 @@ target_link_libraries(mimick_${PROJECT_NAME} INTERFACE benchmark::benchmark mimick_memory_manager) -install(TARGETS ${PROJECT_NAME} mimick_memory_manager mimick_${PROJECT_NAME} +add_library(memory_aware_benchmark_main + src/memory_aware_benchmark_main.cpp + src/memory_aware_console_reporter.cpp) + +target_include_directories(memory_aware_benchmark_main PRIVATE + $ + $) + +target_link_libraries(memory_aware_benchmark_main PUBLIC + benchmark::benchmark) + +target_compile_definitions(memory_aware_benchmark_main PRIVATE + "PERFORMANCE_TEST_FIXTURE_BUILDING_DLL") + +install(TARGETS ${PROJECT_NAME} mimick_memory_manager mimick_${PROJECT_NAME} memory_aware_benchmark_main EXPORT ${PROJECT_NAME} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib @@ -123,10 +137,12 @@ if(BUILD_TESTING) ament_add_google_benchmark( benchmark_malloc_realloc_mimick test/benchmark/benchmark_malloc_realloc_mimick.cpp - TIMEOUT 60) + TIMEOUT 60 + SKIP_LINKING_MAIN_LIBRARIES) if(TARGET benchmark_malloc_realloc_mimick) target_link_libraries(benchmark_malloc_realloc_mimick - mimick_performance_test_fixture) + mimick_performance_test_fixture + memory_aware_benchmark_main) endif() endif() diff --git a/src/memory_aware_benchmark_main.cpp b/src/memory_aware_benchmark_main.cpp new file mode 100644 index 0000000..347c164 --- /dev/null +++ b/src/memory_aware_benchmark_main.cpp @@ -0,0 +1,36 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "performance_test_fixture/visibility_control.hpp" +#include "./memory_aware_console_reporter.hpp" + +constexpr benchmark::ConsoleReporter::OutputOptions output_options = + benchmark::ConsoleReporter::OO_None; + +PERFORMANCE_TEST_FIXTURE_PUBLIC +int main(int argc, char * argv[]) +{ + benchmark::Initialize(&argc, argv); + + if (benchmark::ReportUnrecognizedArguments(argc, argv)) { + return 1; + } + + performance_test_fixture::MemoryAwareConsoleReporter display_reporter(output_options); + benchmark::RunSpecifiedBenchmarks(&display_reporter); + + return 0; +} diff --git a/src/memory_aware_console_reporter.cpp b/src/memory_aware_console_reporter.cpp new file mode 100644 index 0000000..b1ed45a --- /dev/null +++ b/src/memory_aware_console_reporter.cpp @@ -0,0 +1,49 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "./memory_aware_console_reporter.hpp" + +namespace performance_test_fixture +{ + +MemoryAwareConsoleReporter::MemoryAwareConsoleReporter( + benchmark::ConsoleReporter::OutputOptions opts_) +: benchmark::ConsoleReporter(opts_) +{ +} + +void MemoryAwareConsoleReporter::ReportRuns( + const std::vector & reports) +{ + std::vector augmented_reports(reports.begin(), reports.end()); + + for (std::vector::iterator it = augmented_reports.begin(); + it != augmented_reports.end(); + it++) + { + if (it->has_memory_result) { + *it = benchmark::BenchmarkReporter::Run(*it); + it->counters["allocs_per_iter"] = + benchmark::Counter(static_cast(it->allocs_per_iter)); + it->counters["max_bytes_used"] = + benchmark::Counter(static_cast(it->max_bytes_used)); + } + } + + benchmark::ConsoleReporter::ReportRuns(augmented_reports); +} + +} // namespace performance_test_fixture diff --git a/src/memory_aware_console_reporter.hpp b/src/memory_aware_console_reporter.hpp new file mode 100644 index 0000000..d3e122d --- /dev/null +++ b/src/memory_aware_console_reporter.hpp @@ -0,0 +1,35 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEMORY_AWARE_CONSOLE_REPORTER_HPP_ +#define MEMORY_AWARE_CONSOLE_REPORTER_HPP_ + +#include + +#include + +namespace performance_test_fixture +{ + +class MemoryAwareConsoleReporter : public benchmark::ConsoleReporter +{ +public: + explicit MemoryAwareConsoleReporter( + benchmark::ConsoleReporter::OutputOptions opts_ = benchmark::ConsoleReporter::OO_Defaults); + void ReportRuns(const std::vector & reports) override; +}; + +} // namespace performance_test_fixture + +#endif // MEMORY_AWARE_CONSOLE_REPORTER_HPP_ From e4c48b62dd5c70079b07080b40f1281b6e5e23d1 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Mon, 23 Nov 2020 11:12:57 -0800 Subject: [PATCH 03/10] Make MimickMemoryManager::Stop noexcept Signed-off-by: Scott K Logan --- include/performance_test_fixture/mimick_memory_manager.hpp | 2 +- src/mimick_memory_manager.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/performance_test_fixture/mimick_memory_manager.hpp b/include/performance_test_fixture/mimick_memory_manager.hpp index 67c9b8f..b6a19ab 100644 --- a/include/performance_test_fixture/mimick_memory_manager.hpp +++ b/include/performance_test_fixture/mimick_memory_manager.hpp @@ -51,7 +51,7 @@ class MimickMemoryManager : public benchmark::MemoryManager void Stop(benchmark::MemoryManager::Result * result) override; private: - void Stop(); + void Stop() noexcept; void on_free(void * ptr); void * on_malloc(size_t size); diff --git a/src/mimick_memory_manager.cpp b/src/mimick_memory_manager.cpp index b22f953..83bbd6b 100644 --- a/src/mimick_memory_manager.cpp +++ b/src/mimick_memory_manager.cpp @@ -122,7 +122,7 @@ void MimickMemoryManager::Stop(benchmark::MemoryManager::Result * result) stat_lock.unlock(); } -void MimickMemoryManager::Stop() +void MimickMemoryManager::Stop() noexcept { if (MMK_STUB_INVALID != free_stub) { mmk_stub_destroy(free_stub); From 1ea120c0cf193c1b21144b66f3b0087bc9180de4 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Tue, 24 Nov 2020 15:05:07 -0800 Subject: [PATCH 04/10] Switch from map to unordered_set Also store the size of the allocation at the beginning of the allocation. Note that this could fail catastrophically if the code under test overwrites that block, but this approach appears to be more performant. Signed-off-by: Scott K Logan --- .../mimick_memory_manager.hpp | 7 ++- src/mimick_memory_manager.cpp | 53 ++++++++++++------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/include/performance_test_fixture/mimick_memory_manager.hpp b/include/performance_test_fixture/mimick_memory_manager.hpp index b6a19ab..fc9fbea 100644 --- a/include/performance_test_fixture/mimick_memory_manager.hpp +++ b/include/performance_test_fixture/mimick_memory_manager.hpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include #include #include @@ -63,9 +63,8 @@ class MimickMemoryManager : public benchmark::MemoryManager decltype(benchmark::MemoryManager::Result::max_bytes_used) cur_bytes_used; decltype(benchmark::MemoryManager::Result::max_bytes_used) max_bytes_used; decltype(benchmark::MemoryManager::Result::num_allocs) num_allocs; - std::unique_ptr< - std::map, mmk_allocator::value_type>>> ptr_map; + std::unique_ptr, std::equal_to, + mmk_allocator>> ptr_set; struct mmk_stub * free_stub; struct mmk_stub * malloc_stub; diff --git a/src/mimick_memory_manager.cpp b/src/mimick_memory_manager.cpp index 83bbd6b..cb13f5f 100644 --- a/src/mimick_memory_manager.cpp +++ b/src/mimick_memory_manager.cpp @@ -16,7 +16,7 @@ #include #include -#include +#include #include #include "mimick/mimick.h" @@ -30,9 +30,8 @@ MimickMemoryManager::MimickMemoryManager() cur_bytes_used(0), max_bytes_used(0), num_allocs(0), - ptr_map( - new std::map, mmk_allocator::value_type>>()), + ptr_set(new std::unordered_set, std::equal_to, + mmk_allocator>()), free_stub(MMK_STUB_INVALID), malloc_stub(MMK_STUB_INVALID), realloc_stub(MMK_STUB_INVALID) @@ -60,7 +59,7 @@ void MimickMemoryManager::Reset() cur_bytes_used = 0; max_bytes_used = 0; num_allocs = 0; - ptr_map->clear(); + ptr_set->clear(); stat_lock.unlock(); } @@ -85,7 +84,7 @@ void MimickMemoryManager::Start() cur_bytes_used = 0; max_bytes_used = 0; num_allocs = 0; - ptr_map->clear(); + ptr_set->clear(); free_stub = mmk_stub_create_wrapped("free", on_free, this, void *); if (MMK_STUB_INVALID == free_stub) { @@ -143,27 +142,31 @@ void MimickMemoryManager::Stop() noexcept void MimickMemoryManager::on_free(void * ptr) { stat_lock.lock(); - mmk_free(ptr); - auto ph = ptr_map->find(ptr); - if (ph != ptr_map->end()) { - cur_bytes_used -= ph->second; - ptr_map->erase(ph); + auto ph = ptr_set->find(ptr); + if (ph != ptr_set->end()) { + ptr = static_cast(ptr) - 1; + cur_bytes_used -= *static_cast(ptr); + ptr_set->erase(ph); } + mmk_free(ptr); + stat_lock.unlock(); } void * MimickMemoryManager::on_malloc(size_t size) { stat_lock.lock(); - void * new_ptr = mmk_malloc(size); + void * new_ptr = mmk_malloc(sizeof(size_t) + size); if (recording_enabled) { num_allocs++; if (nullptr != new_ptr) { - (*ptr_map)[new_ptr] = size; cur_bytes_used += size; + *static_cast(new_ptr) = size; + new_ptr = static_cast(new_ptr) + 1; + ptr_set->emplace(new_ptr); if (cur_bytes_used > max_bytes_used) { max_bytes_used = cur_bytes_used; } @@ -178,21 +181,31 @@ void * MimickMemoryManager::on_malloc(size_t size) void * MimickMemoryManager::on_realloc(void * ptr, size_t size) { stat_lock.lock(); + + auto ph = ptr_set->find(ptr); + if (ph != ptr_set->end()) { + // Assume that the realloc will succeed + ptr = static_cast(ptr) - 1; + cur_bytes_used -= *static_cast(ptr); + ptr_set->erase(ph); + } + void * new_ptr = mmk_realloc(ptr, size); - if (nullptr != new_ptr || 0 == size) { - auto ph = ptr_map->find(ptr); - if (ph != ptr_map->end()) { - cur_bytes_used -= ph->second; - ptr_map->erase(ph); - } + if ((nullptr == new_ptr && 0 != size)) { + // Realloc failed - undo the removal + cur_bytes_used += *static_cast(ptr); + ptr = static_cast(ptr) + 1; + ptr_set->emplace(ptr); } if (recording_enabled) { num_allocs++; if (nullptr != new_ptr) { - (*ptr_map)[new_ptr] = size; cur_bytes_used += size; + *static_cast(new_ptr) = size; + new_ptr = static_cast(new_ptr) + 1; + ptr_set->emplace(new_ptr); if (cur_bytes_used > max_bytes_used) { max_bytes_used = cur_bytes_used; } From cc62c1d0330b3d148641775dbba6aaff8ab33855 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Tue, 24 Nov 2020 15:15:56 -0800 Subject: [PATCH 05/10] Add a benchmark for scalability of memory tracking Signed-off-by: Scott K Logan --- .../benchmark_malloc_realloc_mimick.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/benchmark/benchmark_malloc_realloc_mimick.cpp b/test/benchmark/benchmark_malloc_realloc_mimick.cpp index fe7e195..b46cc74 100644 --- a/test/benchmark/benchmark_malloc_realloc_mimick.cpp +++ b/test/benchmark/benchmark_malloc_realloc_mimick.cpp @@ -81,6 +81,29 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc)( } } +BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc_many)( + benchmark::State & state) +{ + const size_t malloc_size = state.range(1); + if (malloc_size < 1) { + state.SkipWithError("Size for allocation is too small for this test"); + } + void * ptrs[4096]; + for (auto _ : state) { + for (size_t i = 0; i < 4096; i++) { + ptrs[i] = std::malloc(malloc_size); + if (nullptr == ptrs[i]) { + state.SkipWithError("Malloc failed to malloc"); + } + } + for (size_t i = 0; i < 4096; i++) { + std::free(ptrs[i]); + } + benchmark::DoNotOptimize(ptrs); + benchmark::ClobberMemory(); + } +} + BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( benchmark::State & state) { @@ -122,6 +145,9 @@ static void malloc_args(benchmark::internal::Benchmark * b) BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc) ->ArgNames({"Enable Performance Tracking", "Malloc Size"})->Apply(malloc_args); +BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc_many) +->ArgNames({"Enable Performance Tracking", "Malloc Size"})->Apply(malloc_args); + // Three types of realloc tests, one where malloc is smaller than realloc, one where they are // the same, and one where malloc is larger than realloc. Realloc size ranges from 1 to 2^27 // each time multiplying by 32. Each stop is tested with/without performance metrics From 8e9e2e03dfb66b37d1c009f3795a871fa7449e37 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Tue, 24 Nov 2020 15:57:16 -0800 Subject: [PATCH 06/10] Record calls to calloc The calloc function isn't one of the Mimick "vital" functions, so we don't have a safe function to call in our stub. Since timing performance isn't really a concern here, and our underlying infrastructure can't handle operations bigger than size_t anyway, I just made the stub call malloc and memset to get similar behavior. Signed-off-by: Scott K Logan --- CMakeLists.txt | 2 +- .../mimick_memory_manager.hpp | 2 + src/mimick_memory_manager.cpp | 40 ++++++++++++++++ .../benchmark_malloc_realloc_mimick.cpp | 47 ++++++++++++++----- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4330711..9d82457 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,7 @@ if(BUILD_TESTING) ament_add_google_benchmark( benchmark_malloc_realloc_mimick test/benchmark/benchmark_malloc_realloc_mimick.cpp - TIMEOUT 60 + TIMEOUT 120 SKIP_LINKING_MAIN_LIBRARIES) if(TARGET benchmark_malloc_realloc_mimick) target_link_libraries(benchmark_malloc_realloc_mimick diff --git a/include/performance_test_fixture/mimick_memory_manager.hpp b/include/performance_test_fixture/mimick_memory_manager.hpp index fc9fbea..b24f1da 100644 --- a/include/performance_test_fixture/mimick_memory_manager.hpp +++ b/include/performance_test_fixture/mimick_memory_manager.hpp @@ -53,6 +53,7 @@ class MimickMemoryManager : public benchmark::MemoryManager private: void Stop() noexcept; + void * on_calloc(size_t nitems, size_t size); void on_free(void * ptr); void * on_malloc(size_t size); void * on_realloc(void * ptr, size_t size); @@ -66,6 +67,7 @@ class MimickMemoryManager : public benchmark::MemoryManager std::unique_ptr, std::equal_to, mmk_allocator>> ptr_set; + struct mmk_stub * calloc_stub; struct mmk_stub * free_stub; struct mmk_stub * malloc_stub; struct mmk_stub * realloc_stub; diff --git a/src/mimick_memory_manager.cpp b/src/mimick_memory_manager.cpp index cb13f5f..9585188 100644 --- a/src/mimick_memory_manager.cpp +++ b/src/mimick_memory_manager.cpp @@ -14,6 +14,7 @@ #include "performance_test_fixture/mimick_memory_manager.hpp" +#include #include #include #include @@ -32,6 +33,7 @@ MimickMemoryManager::MimickMemoryManager() num_allocs(0), ptr_set(new std::unordered_set, std::equal_to, mmk_allocator>()), + calloc_stub(MMK_STUB_INVALID), free_stub(MMK_STUB_INVALID), malloc_stub(MMK_STUB_INVALID), realloc_stub(MMK_STUB_INVALID) @@ -86,6 +88,12 @@ void MimickMemoryManager::Start() num_allocs = 0; ptr_set->clear(); + calloc_stub = mmk_stub_create_wrapped("calloc", on_calloc, this, size_t, size_t); + if (MMK_STUB_INVALID == calloc_stub) { + std::cerr << "Failed to create 'calloc' stub!" << std::endl; + exit(1); + } + free_stub = mmk_stub_create_wrapped("free", on_free, this, void *); if (MMK_STUB_INVALID == free_stub) { std::cerr << "Failed to create 'free' stub!" << std::endl; @@ -123,6 +131,11 @@ void MimickMemoryManager::Stop(benchmark::MemoryManager::Result * result) void MimickMemoryManager::Stop() noexcept { + if (MMK_STUB_INVALID != calloc_stub) { + mmk_stub_destroy(calloc_stub); + calloc_stub = MMK_STUB_INVALID; + } + if (MMK_STUB_INVALID != free_stub) { mmk_stub_destroy(free_stub); free_stub = MMK_STUB_INVALID; @@ -139,6 +152,33 @@ void MimickMemoryManager::Stop() noexcept } } +void * MimickMemoryManager::on_calloc(size_t nitems, size_t size) +{ + stat_lock.lock(); + void * new_ptr = mmk_malloc(sizeof(size_t) + (nitems * size)); + + if (nullptr != new_ptr) { + std::memset(new_ptr, 0x0, nitems * size); + } + + if (recording_enabled) { + num_allocs++; + if (nullptr != new_ptr) { + cur_bytes_used += nitems * size; + *static_cast(new_ptr) = nitems * size; + new_ptr = static_cast(new_ptr) + 1; + ptr_set->emplace(new_ptr); + if (cur_bytes_used > max_bytes_used) { + max_bytes_used = cur_bytes_used; + } + } + } + + stat_lock.unlock(); + + return new_ptr; +} + void MimickMemoryManager::on_free(void * ptr) { stat_lock.lock(); diff --git a/test/benchmark/benchmark_malloc_realloc_mimick.cpp b/test/benchmark/benchmark_malloc_realloc_mimick.cpp index b46cc74..3f64565 100644 --- a/test/benchmark/benchmark_malloc_realloc_mimick.cpp +++ b/test/benchmark/benchmark_malloc_realloc_mimick.cpp @@ -66,12 +66,12 @@ class PerformanceTestFixture : public performance_test_fixture::MimickPerformanc BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc)( benchmark::State & state) { - const size_t malloc_size = state.range(1); - if (malloc_size < 1) { + const size_t alloc_size = state.range(1); + if (alloc_size < 1) { state.SkipWithError("Size for allocation is too small for this test"); } for (auto _ : state) { - void * ptr = std::malloc(malloc_size); + void * ptr = std::malloc(alloc_size); if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { state.SkipWithError("Malloc failed to malloc"); } @@ -81,17 +81,35 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc)( } } +BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_calloc)( + benchmark::State & state) +{ + const size_t alloc_size = state.range(1); + if (alloc_size < 1) { + state.SkipWithError("Size for allocation is too small for this test"); + } + for (auto _ : state) { + void * ptr = std::calloc(1, alloc_size); + if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { + state.SkipWithError("Calloc failed to calloc"); + } + std::free(ptr); + benchmark::DoNotOptimize(ptr); + benchmark::ClobberMemory(); + } +} + BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc_many)( benchmark::State & state) { - const size_t malloc_size = state.range(1); - if (malloc_size < 1) { + const size_t alloc_size = state.range(1); + if (alloc_size < 1) { state.SkipWithError("Size for allocation is too small for this test"); } void * ptrs[4096]; for (auto _ : state) { for (size_t i = 0; i < 4096; i++) { - ptrs[i] = std::malloc(malloc_size); + ptrs[i] = std::malloc(alloc_size); if (nullptr == ptrs[i]) { state.SkipWithError("Malloc failed to malloc"); } @@ -107,8 +125,8 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc_many)( BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( benchmark::State & state) { - const int64_t malloc_size = state.range(1); - if (malloc_size < 1) { + const int64_t alloc_size = state.range(1); + if (alloc_size < 1) { state.SkipWithError("Size for allocation is too small for this test"); } const size_t realloc_size = state.range(2); @@ -116,7 +134,7 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( state.SkipWithError("Size for reallocation is too small for this test"); } for (auto _ : state) { - void * ptr = std::malloc(malloc_size); + void * ptr = std::malloc(alloc_size); if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { state.SkipWithError("Malloc failed to malloc"); } @@ -133,7 +151,7 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( // Malloc sizes Range from 1 to 2^27 each time multiplying by 16. Each value tested // with/without performance metrics -static void malloc_args(benchmark::internal::Benchmark * b) +static void alloc_args(benchmark::internal::Benchmark * b) { for (int64_t shift_left = 0; shift_left < 32; shift_left += 4) { b->Args({kDisablePerformanceTracking, 1ll << shift_left}); @@ -143,10 +161,13 @@ static void malloc_args(benchmark::internal::Benchmark * b) } BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc) -->ArgNames({"Enable Performance Tracking", "Malloc Size"})->Apply(malloc_args); +->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); + +BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_calloc) +->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc_many) -->ArgNames({"Enable Performance Tracking", "Malloc Size"})->Apply(malloc_args); +->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); // Three types of realloc tests, one where malloc is smaller than realloc, one where they are // the same, and one where malloc is larger than realloc. Realloc size ranges from 1 to 2^27 @@ -167,5 +188,5 @@ static void realloc_args(benchmark::internal::Benchmark * b) } BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_realloc) -->ArgNames({"Enable Performance Tracking", "Malloc Size", "Realloc Size"}) +->ArgNames({"Enable Performance Tracking", "Alloc Size", "Realloc Size"}) ->Apply(realloc_args); From 79a28fef9b93bac4bbd271f285a16664be57ec4f Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Tue, 24 Nov 2020 17:27:52 -0800 Subject: [PATCH 07/10] Reduce size of allocation for 'many' test Signed-off-by: Scott K Logan --- test/benchmark/benchmark_malloc_realloc_mimick.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/benchmark/benchmark_malloc_realloc_mimick.cpp b/test/benchmark/benchmark_malloc_realloc_mimick.cpp index 3f64565..cf8b6b8 100644 --- a/test/benchmark/benchmark_malloc_realloc_mimick.cpp +++ b/test/benchmark/benchmark_malloc_realloc_mimick.cpp @@ -166,8 +166,18 @@ BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc) BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_calloc) ->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); +static void alloc_many_args(benchmark::internal::Benchmark * b) +{ + for (int64_t shift_left = 0; shift_left < 24; shift_left += 4) { + b->Args({kDisablePerformanceTracking, 1ll << shift_left}); + b->Args({kEnablePerformanceTracking, 1ll << shift_left}); + b->Args({kForcePerformanceTracking, 1ll << shift_left}); + } +} + + BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc_many) -->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); +->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_many_args); // Three types of realloc tests, one where malloc is smaller than realloc, one where they are // the same, and one where malloc is larger than realloc. Realloc size ranges from 1 to 2^27 From 5e026e0417438018f606d476ecd677111a813329 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Tue, 1 Dec 2020 15:12:31 -0800 Subject: [PATCH 08/10] Add branch hint in test Signed-off-by: Scott K Logan --- test/benchmark/benchmark_malloc_realloc_mimick.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/benchmark/benchmark_malloc_realloc_mimick.cpp b/test/benchmark/benchmark_malloc_realloc_mimick.cpp index cf8b6b8..e728889 100644 --- a/test/benchmark/benchmark_malloc_realloc_mimick.cpp +++ b/test/benchmark/benchmark_malloc_realloc_mimick.cpp @@ -110,7 +110,7 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc_many)( for (auto _ : state) { for (size_t i = 0; i < 4096; i++) { ptrs[i] = std::malloc(alloc_size); - if (nullptr == ptrs[i]) { + if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptrs[i])) { state.SkipWithError("Malloc failed to malloc"); } } From 1ce398ac899d450b0f943cd3eb7ad099e08ec3af Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Tue, 1 Dec 2020 15:45:30 -0800 Subject: [PATCH 09/10] Switch PerformanceTestFixture to use Mimick After showing that this approach works, this commit actually swaps the PerformanceTestFixture from using osrf_testing_tools_cpp to using Mimick. Signed-off-by: Scott K Logan --- CMakeLists.txt | 75 +------ cmake/add_performance_test.cmake | 23 +- include/performance_test_fixture/common.hpp | 37 ---- .../mimick_performance_test_fixture.hpp | 47 ---- .../performance_test_fixture.hpp | 38 ++-- package.xml | 3 +- performance_test_fixture-extras.cmake | 26 --- src/mimick_performance_test_fixture.cpp | 77 ------- src/performance_test_fixture.cpp | 106 ++++----- test/benchmark/benchmark_malloc_realloc.cpp | 70 ++++-- .../benchmark_malloc_realloc_mimick.cpp | 202 ------------------ ...nchmark_malloc_realloc_no_memory_tools.cpp | 103 --------- 12 files changed, 131 insertions(+), 676 deletions(-) delete mode 100644 include/performance_test_fixture/common.hpp delete mode 100644 include/performance_test_fixture/mimick_performance_test_fixture.hpp delete mode 100644 src/mimick_performance_test_fixture.cpp delete mode 100644 test/benchmark/benchmark_malloc_realloc_mimick.cpp delete mode 100644 test/benchmark/benchmark_malloc_realloc_no_memory_tools.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ab532ab..a68114f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,9 +19,11 @@ find_package(ament_cmake_export_targets REQUIRED) find_package(ament_cmake_test REQUIRED) find_package(benchmark REQUIRED) find_package(mimick_vendor REQUIRED) -find_package(osrf_testing_tools_cpp REQUIRED) -add_library(${PROJECT_NAME} +# TODO(cottsay): It would be great if this didn't need to be STATIC, but +# libmimick is specifically compiled static and without PIC. +add_library(${PROJECT_NAME} STATIC + src/mimick_memory_manager.cpp src/${PROJECT_NAME}.cpp) target_include_directories(${PROJECT_NAME} PUBLIC @@ -30,42 +32,11 @@ target_include_directories(${PROJECT_NAME} PUBLIC target_link_libraries(${PROJECT_NAME} PUBLIC benchmark::benchmark - osrf_testing_tools_cpp::memory_tools) + mimick) target_compile_definitions(${PROJECT_NAME} PRIVATE "PERFORMANCE_TEST_FIXTURE_BUILDING_DLL") -# TODO(cottsay): It would be great if this didn't need to be STATIC, but -# libmimick is specifically compiled static and without PIC. -add_library(mimick_memory_manager STATIC - src/mimick_memory_manager.cpp) - -target_include_directories(mimick_memory_manager PUBLIC - $ - $) - -target_include_directories(mimick_memory_manager SYSTEM PRIVATE - $ - $) - -target_link_libraries(mimick_memory_manager INTERFACE - benchmark::benchmark - mimick) - -add_library(mimick_${PROJECT_NAME} STATIC - src/mimick_${PROJECT_NAME}.cpp) - -target_include_directories(mimick_${PROJECT_NAME} PUBLIC - $ - $) - -target_include_directories(mimick_${PROJECT_NAME} SYSTEM PRIVATE - $) - -target_link_libraries(mimick_${PROJECT_NAME} INTERFACE - benchmark::benchmark - mimick_memory_manager) - add_library(memory_aware_benchmark_main src/memory_aware_benchmark_main.cpp src/memory_aware_console_reporter.cpp) @@ -80,7 +51,7 @@ target_link_libraries(memory_aware_benchmark_main PUBLIC target_compile_definitions(memory_aware_benchmark_main PRIVATE "PERFORMANCE_TEST_FIXTURE_BUILDING_DLL") -install(TARGETS ${PROJECT_NAME} mimick_memory_manager mimick_${PROJECT_NAME} memory_aware_benchmark_main +install(TARGETS ${PROJECT_NAME} memory_aware_benchmark_main EXPORT ${PROJECT_NAME} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib @@ -98,8 +69,7 @@ ament_export_targets(${PROJECT_NAME}) ament_export_dependencies( ament_cmake_google_benchmark benchmark - mimick_vendor - osrf_testing_tools_cpp) + mimick_vendor) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) @@ -107,44 +77,17 @@ if(BUILD_TESTING) set(performance_test_fixture_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) + add_library(${PROJECT_NAME}::memory_aware_benchmark_main ALIAS memory_aware_benchmark_main) include(performance_test_fixture-extras.cmake) add_performance_test( benchmark_malloc_realloc test/benchmark/benchmark_malloc_realloc.cpp - TIMEOUT 90) - - # Bypassing performance_test_fixture to avoid LD_PRELOAD of - # osrf_testing_tools_cpp - set(_no_mem_tools_skip_arg) - if(TARGET benchmark_malloc_realloc) - get_target_property(ARG_SKIP_RETURN_CODE benchmark_malloc_realloc SKIP_RETURN_CODE) - if(ARG_SKIP_RETURN_CODE) - set(_no_mem_tools_skip_arg SKIP_TEST) - endif() - else() - set(_no_mem_tools_skip_arg SKIP_TEST) - endif() - ament_add_google_benchmark( - benchmark_malloc_realloc_no_memory_tools - test/benchmark/benchmark_malloc_realloc_no_memory_tools.cpp - ${_no_mem_tools_skip_arg} - TIMEOUT 60) + TIMEOUT 120) add_performance_test( benchmark_pause_resume test/benchmark/benchmark_pause_resume.cpp) - - ament_add_google_benchmark( - benchmark_malloc_realloc_mimick - test/benchmark/benchmark_malloc_realloc_mimick.cpp - TIMEOUT 120 - SKIP_LINKING_MAIN_LIBRARIES) - if(TARGET benchmark_malloc_realloc_mimick) - target_link_libraries(benchmark_malloc_realloc_mimick - mimick_performance_test_fixture - memory_aware_benchmark_main) - endif() endif() ament_package( diff --git a/cmake/add_performance_test.cmake b/cmake/add_performance_test.cmake index affa6da..642e92c 100644 --- a/cmake/add_performance_test.cmake +++ b/cmake/add_performance_test.cmake @@ -14,7 +14,7 @@ # # Add a google benchmark test which utilizes performance_test_fixture to -# leverage osrf_testing_tools_cpp. +# leverage Mimick for memory recording. # # Call add_executable(target ARGN), link it against the google benchmark # libraries and register the executable as a test. @@ -22,8 +22,6 @@ # If google benchmark is not available the specified target is not being created # and therefore the target existence should be checked before being used. # -# If osrf_testing_tools_cpp memory_tools is not available the test is skipped. -# # :param target: the target name which will also be used as the test name # :type target: string # :param ARGN: the list of source files @@ -65,27 +63,16 @@ macro(add_performance_test target) endif() # add executable - set(_argn_executable ${_ARG_UNPARSED_ARGUMENTS}) - if(_ARG_SKIP_LINKING_MAIN_LIBRARIES) - list(APPEND _argn_executable "SKIP_LINKING_MAIN_LIBRARIES") - endif() + set(_argn_executable ${_ARG_UNPARSED_ARGUMENTS} "SKIP_LINKING_MAIN_LIBRARIES") ament_add_google_benchmark_executable("${target}" ${_argn_executable}) if(TARGET ${target}) - _performance_test_fixture_find_memory_tools() - target_link_libraries(${target} - osrf_testing_tools_cpp::memory_tools performance_test_fixture::performance_test_fixture) - if(_PERFORMANCE_TEST_FIXTURE_MEMORY_TOOLS_AVAILABLE) - if(NOT _ARG_ENV) - set(_ARG_ENV "") - endif() - list(APPEND _ARG_ENV - ${_PERFORMANCE_TEST_FIXTURE_MEMORY_TOOLS_ENV}) - else() - set(_ARG_SKIP_TEST TRUE) + if(NOT _ARG_SKIP_LINKING_MAIN_LIBRARIES) + target_link_libraries(${target} + performance_test_fixture::memory_aware_benchmark_main) endif() endif() diff --git a/include/performance_test_fixture/common.hpp b/include/performance_test_fixture/common.hpp deleted file mode 100644 index a7f2519..0000000 --- a/include/performance_test_fixture/common.hpp +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2020 Open Source Robotics Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef PERFORMANCE_TEST_FIXTURE__COMMON_HPP_ -#define PERFORMANCE_TEST_FIXTURE__COMMON_HPP_ - -/** - * Macro to pause timing and heap allocation measurements over a section of code. - * - * This is useful if there is setup or teardown that has to occur with every iteration. - * For example, if you wanted to measure only construction of a rclcpp::Node and not its - * destruction. - * - * state.PauseTiming() does not allocate, so it should go first to minimize discrepencies in - * measuring timing - */ -#define PERFORMANCE_TEST_FIXTURE_PAUSE_MEASUREMENTS(state, code) \ - do { \ - state.PauseTiming(); \ - set_are_allocation_measurements_active(false); \ - code; \ - set_are_allocation_measurements_active(true); \ - state.ResumeTiming(); \ - } while (0) - -#endif // PERFORMANCE_TEST_FIXTURE__COMMON_HPP_ diff --git a/include/performance_test_fixture/mimick_performance_test_fixture.hpp b/include/performance_test_fixture/mimick_performance_test_fixture.hpp deleted file mode 100644 index 199182f..0000000 --- a/include/performance_test_fixture/mimick_performance_test_fixture.hpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2020 Open Source Robotics Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef PERFORMANCE_TEST_FIXTURE__MIMICK_PERFORMANCE_TEST_FIXTURE_HPP_ -#define PERFORMANCE_TEST_FIXTURE__MIMICK_PERFORMANCE_TEST_FIXTURE_HPP_ - -#include - -#include - -#include "performance_test_fixture/common.hpp" -#include "performance_test_fixture/mimick_memory_manager.hpp" - -namespace performance_test_fixture -{ - -class MimickPerformanceTest : public benchmark::Fixture -{ -public: - MimickPerformanceTest(); - - void SetUp(benchmark::State & state) override; - - void TearDown(benchmark::State & state) override; - -protected: - void reset_heap_counters(); - - void set_are_allocation_measurements_active(bool value); - - std::unique_ptr memory_manager; -}; - -} // namespace performance_test_fixture - -#endif // PERFORMANCE_TEST_FIXTURE__MIMICK_PERFORMANCE_TEST_FIXTURE_HPP_ diff --git a/include/performance_test_fixture/performance_test_fixture.hpp b/include/performance_test_fixture/performance_test_fixture.hpp index d63d8a1..d144b69 100644 --- a/include/performance_test_fixture/performance_test_fixture.hpp +++ b/include/performance_test_fixture/performance_test_fixture.hpp @@ -16,12 +16,10 @@ #define PERFORMANCE_TEST_FIXTURE__PERFORMANCE_TEST_FIXTURE_HPP_ #include -#include -#include +#include -#include "performance_test_fixture/common.hpp" -#include "performance_test_fixture/visibility_control.hpp" +#include "performance_test_fixture/mimick_memory_manager.hpp" namespace performance_test_fixture { @@ -29,31 +27,39 @@ namespace performance_test_fixture class PerformanceTest : public benchmark::Fixture { public: - PERFORMANCE_TEST_FIXTURE_PUBLIC PerformanceTest(); - PERFORMANCE_TEST_FIXTURE_PUBLIC void SetUp(benchmark::State & state) override; - PERFORMANCE_TEST_FIXTURE_PUBLIC void TearDown(benchmark::State & state) override; protected: - PERFORMANCE_TEST_FIXTURE_PUBLIC - void on_alloc(osrf_testing_tools_cpp::memory_tools::MemoryToolsService & service); - - PERFORMANCE_TEST_FIXTURE_PUBLIC void reset_heap_counters(); - PERFORMANCE_TEST_FIXTURE_PUBLIC void set_are_allocation_measurements_active(bool value); -private: - std::atomic_size_t allocation_count; - bool suppress_memory_tools_logging; - bool are_allocation_measurements_active; + std::unique_ptr memory_manager; }; } // namespace performance_test_fixture +/** + * Macro to pause timing and heap allocation measurements over a section of code. + * + * This is useful if there is setup or teardown that has to occur with every iteration. + * For example, if you wanted to measure only construction of a rclcpp::Node and not its + * destruction. + * + * state.PauseTiming() does not allocate, so it should go first to minimize discrepencies in + * measuring timing + */ +#define PERFORMANCE_TEST_FIXTURE_PAUSE_MEASUREMENTS(state, code) \ + do { \ + state.PauseTiming(); \ + set_are_allocation_measurements_active(false); \ + code; \ + set_are_allocation_measurements_active(true); \ + state.ResumeTiming(); \ + } while (0) + #endif // PERFORMANCE_TEST_FIXTURE__PERFORMANCE_TEST_FIXTURE_HPP_ diff --git a/package.xml b/package.xml index 904eb5b..efec6ee 100644 --- a/package.xml +++ b/package.xml @@ -4,7 +4,7 @@ performance_test_fixture 0.0.6 - Test fixture and CMake macro for using osrf_testing_tools_cpp with Google Benchmark + Test fixture and CMake macro for using Mimick with Google Benchmark Alejandro Hernandez Cordero Apache License 2.0 @@ -20,7 +20,6 @@ google_benchmark_vendor mimick_vendor - osrf_testing_tools_cpp ament_cmake_google_benchmark ament_lint_auto diff --git a/performance_test_fixture-extras.cmake b/performance_test_fixture-extras.cmake index 6f57e01..bd99326 100644 --- a/performance_test_fixture-extras.cmake +++ b/performance_test_fixture-extras.cmake @@ -12,30 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -macro(_performance_test_fixture_find_memory_tools) - if(NOT DEFINED _PERFORMANCE_TEST_FIXTURE_FIND_MEMORY_TOOLS) - set(_PERFORMANCE_TEST_FIXTURE_FIND_MEMORY_TOOLS TRUE) - find_package(osrf_testing_tools_cpp REQUIRED) - - get_target_property( - _PERFORMANCE_TEST_FIXTURE_MEMORY_TOOLS_AVAILABLE - osrf_testing_tools_cpp::memory_tools - LIBRARY_PRELOAD_ENVIRONMENT_IS_AVAILABLE) - - if(NOT _PERFORMANCE_TEST_FIXTURE_MEMORY_TOOLS_AVAILABLE) - if(AMENT_RUN_PERFORMANCE_TESTS) - message(WARNING - "'osrf_testing_tools_cpp' memory tools are not available, C++ tests " - "using 'performance_test_fixture' can not be run and will be " - "skipped.") - endif() - else() - get_target_property( - _PERFORMANCE_TEST_FIXTURE_MEMORY_TOOLS_ENV - osrf_testing_tools_cpp::memory_tools - LIBRARY_PRELOAD_ENVIRONMENT_VARIABLE) - endif() - endif() -endmacro() - include("${performance_test_fixture_DIR}/add_performance_test.cmake") diff --git a/src/mimick_performance_test_fixture.cpp b/src/mimick_performance_test_fixture.cpp deleted file mode 100644 index 98f474d..0000000 --- a/src/mimick_performance_test_fixture.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2020 Open Source Robotics Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "performance_test_fixture/mimick_performance_test_fixture.hpp" - -namespace performance_test_fixture -{ - -/* - * This is a lightweight MemoryManager wrapper that unregisters itself when it - * is asked to stop recording. This is necessary so that when a benchmark - * executable contains multiple tests and not all of them use this fixture, - * the memory manager (which is stored in Google Benchmark as a global) isn't - * registered despite the absence of this fixture. - * - * Since that behavior is specific to our use of a fixture to take care of - * the MemoryManager registration, it isn't intrinsic to the MemoryManager - * concept in general, which is why this class is implemented here. - */ -template -class AutoStopMemoryManager : public U -{ -public: - void Stop(benchmark::MemoryManager::Result * result) override - { - U::Stop(result); - benchmark::RegisterMemoryManager(nullptr); - } -}; - -MimickPerformanceTest::MimickPerformanceTest() -: memory_manager(new AutoStopMemoryManager()) -{ -} - -void MimickPerformanceTest::SetUp(benchmark::State &) -{ - // Registering the MemoryManager doesn't mean that it will be used in the - // current run. Benchmark does the timing measurements first, so this will - // cause the MemoryManager to be registered during those runs, but it won't - // be actually started until the registration is checked after the timing - // runs are finished. - benchmark::RegisterMemoryManager(memory_manager.get()); - memory_manager->Reset(); -} - -void MimickPerformanceTest::TearDown(benchmark::State &) -{ - memory_manager->Pause(); -} - -void MimickPerformanceTest::reset_heap_counters() -{ - memory_manager->Reset(); -} - -void MimickPerformanceTest::set_are_allocation_measurements_active(bool value) -{ - if (value) { - memory_manager->Resume(); - } else { - memory_manager->Pause(); - } -} - -} // namespace performance_test_fixture diff --git a/src/performance_test_fixture.cpp b/src/performance_test_fixture.cpp index 6eeb64b..49221c7 100644 --- a/src/performance_test_fixture.cpp +++ b/src/performance_test_fixture.cpp @@ -14,98 +14,64 @@ #include "performance_test_fixture/performance_test_fixture.hpp" -#include -#include -#include - -#ifdef _WIN32 -#pragma warning(disable : 4996) -#endif - namespace performance_test_fixture { -PerformanceTest::PerformanceTest() -: suppress_memory_tools_logging(true), - are_allocation_measurements_active(false) +/* + * This is a lightweight MemoryManager wrapper that unregisters itself when it + * is asked to stop recording. This is necessary so that when a benchmark + * executable contains multiple tests and not all of them use this fixture, + * the memory manager (which is stored in Google Benchmark as a global) isn't + * registered despite the absence of this fixture. + * + * Since that behavior is specific to our use of a fixture to take care of + * the MemoryManager registration, it isn't intrinsic to the MemoryManager + * concept in general, which is why this class is implemented here. + */ +template +class AutoStopMemoryManager : public U { - const char * performance_test_fixture_enable_trace = getenv( - "PERFORMANCE_TEST_FIXTURE_ENABLE_TRACE"); - if (nullptr != performance_test_fixture_enable_trace && - strcmp("1", performance_test_fixture_enable_trace) == 0) +public: + void Stop(benchmark::MemoryManager::Result * result) override { - suppress_memory_tools_logging = false; + U::Stop(result); + benchmark::RegisterMemoryManager(nullptr); } +}; - ComputeStatistics( - "max", - [](const std::vector & v) -> double { - return *(std::max_element(std::begin(v), std::end(v))); - }); - ComputeStatistics( - "min", - [](const std::vector & v) -> double { - return *(std::min_element(std::begin(v), std::end(v))); - }); -} - -void PerformanceTest::SetUp(benchmark::State &) +PerformanceTest::PerformanceTest() +: memory_manager(new AutoStopMemoryManager()) { - reset_heap_counters(); - - osrf_testing_tools_cpp::memory_tools::initialize(); - osrf_testing_tools_cpp::memory_tools::on_unexpected_calloc( - std::function( - std::bind(&PerformanceTest::on_alloc, this, std::placeholders::_1))); - osrf_testing_tools_cpp::memory_tools::on_unexpected_malloc( - std::function( - std::bind(&PerformanceTest::on_alloc, this, std::placeholders::_1))); - osrf_testing_tools_cpp::memory_tools::on_unexpected_realloc( - std::function( - std::bind(&PerformanceTest::on_alloc, this, std::placeholders::_1))); - osrf_testing_tools_cpp::memory_tools::enable_monitoring(); - osrf_testing_tools_cpp::memory_tools::expect_no_calloc_begin(); - osrf_testing_tools_cpp::memory_tools::expect_no_malloc_begin(); - osrf_testing_tools_cpp::memory_tools::expect_no_realloc_begin(); } -void PerformanceTest::TearDown(benchmark::State & state) +void PerformanceTest::SetUp(benchmark::State &) { - osrf_testing_tools_cpp::memory_tools::expect_no_calloc_end(); - osrf_testing_tools_cpp::memory_tools::expect_no_malloc_end(); - osrf_testing_tools_cpp::memory_tools::expect_no_realloc_end(); - - if (osrf_testing_tools_cpp::memory_tools::is_working()) { - state.counters["heap_allocations"] = benchmark::Counter( - static_cast(allocation_count), - benchmark::Counter::kAvgIterations); - } - - osrf_testing_tools_cpp::memory_tools::disable_monitoring(); - osrf_testing_tools_cpp::memory_tools::uninitialize(); + // Registering the MemoryManager doesn't mean that it will be used in the + // current run. Benchmark does the timing measurements first, so this will + // cause the MemoryManager to be registered during those runs, but it won't + // be actually started until the registration is checked after the timing + // runs are finished. + benchmark::RegisterMemoryManager(memory_manager.get()); + memory_manager->Reset(); } -void PerformanceTest::on_alloc( - osrf_testing_tools_cpp::memory_tools::MemoryToolsService & service -) +void PerformanceTest::TearDown(benchmark::State &) { - // Refraining from using an if-branch here in performance-critical code - allocation_count += static_cast(are_allocation_measurements_active); - - if (suppress_memory_tools_logging) { - service.ignore(); - } + memory_manager->Pause(); } void PerformanceTest::reset_heap_counters() { - allocation_count = 0; - are_allocation_measurements_active = true; + memory_manager->Reset(); } void PerformanceTest::set_are_allocation_measurements_active(bool value) { - are_allocation_measurements_active = value; + if (value) { + memory_manager->Resume(); + } else { + memory_manager->Pause(); + } } } // namespace performance_test_fixture diff --git a/test/benchmark/benchmark_malloc_realloc.cpp b/test/benchmark/benchmark_malloc_realloc.cpp index f1a5d89..1b2373d 100644 --- a/test/benchmark/benchmark_malloc_realloc.cpp +++ b/test/benchmark/benchmark_malloc_realloc.cpp @@ -21,6 +21,7 @@ namespace { +constexpr int kForcePerformanceTracking = 2; constexpr int kEnablePerformanceTracking = 1; constexpr int kDisablePerformanceTracking = 0; @@ -31,15 +32,21 @@ class PerformanceTestFixture : public performance_test_fixture::PerformanceTest public: void SetUp(benchmark::State & state) { - if (kEnablePerformanceTracking != state.range(0) && - kDisablePerformanceTracking != state.range(0)) - { - std::string message = - std::string("Invalid enable/disable value: ") + std::to_string(state.range(0)); - state.SkipWithError(message.c_str()); - } - if (kEnablePerformanceTracking == state.range(0)) { - performance_test_fixture::PerformanceTest::SetUp(state); + switch (state.range(0)) { + case kDisablePerformanceTracking: + break; + case kEnablePerformanceTracking: + performance_test_fixture::PerformanceTest::SetUp(state); + break; + case kForcePerformanceTracking: + performance_test_fixture::PerformanceTest::SetUp(state); + memory_manager->Start(); + break; + default: + std::string message = + std::string("Invalid enable/disable value: ") + std::to_string(state.range(0)); + state.SkipWithError(message.c_str()); + break; } } @@ -47,6 +54,8 @@ class PerformanceTestFixture : public performance_test_fixture::PerformanceTest { if (kEnablePerformanceTracking == state.range(0)) { performance_test_fixture::PerformanceTest::TearDown(state); + } else if (kForcePerformanceTracking == state.range(0)) { + memory_manager->Pause(); } } }; @@ -90,11 +99,34 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_calloc)( } } +BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc_many)( + benchmark::State & state) +{ + const size_t alloc_size = state.range(1); + if (alloc_size < 1) { + state.SkipWithError("Size for allocation is too small for this test"); + } + void * ptrs[4096]; + for (auto _ : state) { + for (size_t i = 0; i < 4096; i++) { + ptrs[i] = std::malloc(alloc_size); + if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptrs[i])) { + state.SkipWithError("Malloc failed to malloc"); + } + } + for (size_t i = 0; i < 4096; i++) { + std::free(ptrs[i]); + } + benchmark::DoNotOptimize(ptrs); + benchmark::ClobberMemory(); + } +} + BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( benchmark::State & state) { - const int64_t malloc_size = state.range(1); - if (malloc_size < 1) { + const int64_t alloc_size = state.range(1); + if (alloc_size < 1) { state.SkipWithError("Size for allocation is too small for this test"); } const size_t realloc_size = state.range(2); @@ -102,7 +134,7 @@ BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( state.SkipWithError("Size for reallocation is too small for this test"); } for (auto _ : state) { - void * ptr = std::malloc(malloc_size); + void * ptr = std::malloc(alloc_size); if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { state.SkipWithError("Malloc failed to malloc"); } @@ -124,6 +156,7 @@ static void alloc_args(benchmark::internal::Benchmark * b) for (int64_t shift_left = 0; shift_left < 32; shift_left += 4) { b->Args({kDisablePerformanceTracking, 1ll << shift_left}); b->Args({kEnablePerformanceTracking, 1ll << shift_left}); + b->Args({kForcePerformanceTracking, 1ll << shift_left}); } } @@ -133,6 +166,18 @@ BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc) BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_calloc) ->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); +static void alloc_many_args(benchmark::internal::Benchmark * b) +{ + for (int64_t shift_left = 0; shift_left < 24; shift_left += 4) { + b->Args({kDisablePerformanceTracking, 1ll << shift_left}); + b->Args({kEnablePerformanceTracking, 1ll << shift_left}); + b->Args({kForcePerformanceTracking, 1ll << shift_left}); + } +} + +BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc_many) +->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_many_args); + // Three types of realloc tests, one where malloc is smaller than realloc, one where they are // the same, and one where malloc is larger than realloc. Realloc size ranges from 1 to 2^27 // each time multiplying by 32. Each stop is tested with/without performance metrics @@ -146,6 +191,7 @@ static void realloc_args(benchmark::internal::Benchmark * b) } b->Args({kDisablePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); b->Args({kEnablePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); + b->Args({kForcePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); } } } diff --git a/test/benchmark/benchmark_malloc_realloc_mimick.cpp b/test/benchmark/benchmark_malloc_realloc_mimick.cpp deleted file mode 100644 index e728889..0000000 --- a/test/benchmark/benchmark_malloc_realloc_mimick.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2020 Open Source Robotics Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include - -#include "./macros.h" -#include "performance_test_fixture/mimick_performance_test_fixture.hpp" - -namespace -{ - -constexpr int kForcePerformanceTracking = 2; -constexpr int kEnablePerformanceTracking = 1; -constexpr int kDisablePerformanceTracking = 0; - -} // namespace - -class PerformanceTestFixture : public performance_test_fixture::MimickPerformanceTest -{ -public: - void SetUp(benchmark::State & state) - { - switch (state.range(0)) { - case kDisablePerformanceTracking: - break; - case kEnablePerformanceTracking: - performance_test_fixture::MimickPerformanceTest::SetUp(state); - break; - case kForcePerformanceTracking: - performance_test_fixture::MimickPerformanceTest::SetUp(state); - memory_manager->Start(); - break; - default: - std::string message = - std::string("Invalid enable/disable value: ") + std::to_string(state.range(0)); - state.SkipWithError(message.c_str()); - break; - } - } - - void TearDown(benchmark::State & state) - { - if (kEnablePerformanceTracking == state.range(0)) { - performance_test_fixture::MimickPerformanceTest::TearDown(state); - } else if (kForcePerformanceTracking == state.range(0)) { - memory_manager->Pause(); - } - } -}; - -// This does not make use of PauseTiming or ResumeTiming because timing is very short for these -// benchmarks. However, they should be enough to measure the overhead of turning on performance -// measurements. -BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc)( - benchmark::State & state) -{ - const size_t alloc_size = state.range(1); - if (alloc_size < 1) { - state.SkipWithError("Size for allocation is too small for this test"); - } - for (auto _ : state) { - void * ptr = std::malloc(alloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { - state.SkipWithError("Malloc failed to malloc"); - } - std::free(ptr); - benchmark::DoNotOptimize(ptr); - benchmark::ClobberMemory(); - } -} - -BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_calloc)( - benchmark::State & state) -{ - const size_t alloc_size = state.range(1); - if (alloc_size < 1) { - state.SkipWithError("Size for allocation is too small for this test"); - } - for (auto _ : state) { - void * ptr = std::calloc(1, alloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { - state.SkipWithError("Calloc failed to calloc"); - } - std::free(ptr); - benchmark::DoNotOptimize(ptr); - benchmark::ClobberMemory(); - } -} - -BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_malloc_many)( - benchmark::State & state) -{ - const size_t alloc_size = state.range(1); - if (alloc_size < 1) { - state.SkipWithError("Size for allocation is too small for this test"); - } - void * ptrs[4096]; - for (auto _ : state) { - for (size_t i = 0; i < 4096; i++) { - ptrs[i] = std::malloc(alloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptrs[i])) { - state.SkipWithError("Malloc failed to malloc"); - } - } - for (size_t i = 0; i < 4096; i++) { - std::free(ptrs[i]); - } - benchmark::DoNotOptimize(ptrs); - benchmark::ClobberMemory(); - } -} - -BENCHMARK_DEFINE_F(PerformanceTestFixture, benchmark_on_realloc)( - benchmark::State & state) -{ - const int64_t alloc_size = state.range(1); - if (alloc_size < 1) { - state.SkipWithError("Size for allocation is too small for this test"); - } - const size_t realloc_size = state.range(2); - if (realloc_size < 1) { - state.SkipWithError("Size for reallocation is too small for this test"); - } - for (auto _ : state) { - void * ptr = std::malloc(alloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { - state.SkipWithError("Malloc failed to malloc"); - } - void * realloc_ptr = std::realloc(ptr, realloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == realloc_ptr)) { - std::free(ptr); - state.SkipWithError("Malloc failed to realloc"); - } - std::free(realloc_ptr); - benchmark::DoNotOptimize(realloc_ptr); - benchmark::ClobberMemory(); - } -} - -// Malloc sizes Range from 1 to 2^27 each time multiplying by 16. Each value tested -// with/without performance metrics -static void alloc_args(benchmark::internal::Benchmark * b) -{ - for (int64_t shift_left = 0; shift_left < 32; shift_left += 4) { - b->Args({kDisablePerformanceTracking, 1ll << shift_left}); - b->Args({kEnablePerformanceTracking, 1ll << shift_left}); - b->Args({kForcePerformanceTracking, 1ll << shift_left}); - } -} - -BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc) -->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); - -BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_calloc) -->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_args); - -static void alloc_many_args(benchmark::internal::Benchmark * b) -{ - for (int64_t shift_left = 0; shift_left < 24; shift_left += 4) { - b->Args({kDisablePerformanceTracking, 1ll << shift_left}); - b->Args({kEnablePerformanceTracking, 1ll << shift_left}); - b->Args({kForcePerformanceTracking, 1ll << shift_left}); - } -} - - -BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_malloc_many) -->ArgNames({"Enable Performance Tracking", "Alloc Size"})->Apply(alloc_many_args); - -// Three types of realloc tests, one where malloc is smaller than realloc, one where they are -// the same, and one where malloc is larger than realloc. Realloc size ranges from 1 to 2^27 -// each time multiplying by 32. Each stop is tested with/without performance metrics -static void realloc_args(benchmark::internal::Benchmark * b) -{ - for (int64_t malloc_adjustment = -1; malloc_adjustment <= 1; ++malloc_adjustment) { - for (int64_t realloc_shift = 0; realloc_shift < 32; realloc_shift += 8) { - const int64_t malloc_shift = realloc_shift + malloc_adjustment; - if (malloc_shift < 0) { - continue; - } - b->Args({kDisablePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); - b->Args({kEnablePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); - b->Args({kForcePerformanceTracking, 1ll << malloc_shift, 1ll << realloc_shift}); - } - } -} - -BENCHMARK_REGISTER_F(PerformanceTestFixture, benchmark_on_realloc) -->ArgNames({"Enable Performance Tracking", "Alloc Size", "Realloc Size"}) -->Apply(realloc_args); diff --git a/test/benchmark/benchmark_malloc_realloc_no_memory_tools.cpp b/test/benchmark/benchmark_malloc_realloc_no_memory_tools.cpp deleted file mode 100644 index 0410446..0000000 --- a/test/benchmark/benchmark_malloc_realloc_no_memory_tools.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2020 Open Source Robotics Foundation, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include - -#include "./macros.h" - -// This does not make use of PauseTiming or ResumeTiming because timing is very short for these -// benchmarks. However, they should allow for comparisons to the other benchmark_malloc_realloc -static void benchmark_on_malloc(benchmark::State & state) -{ - const size_t alloc_size = state.range(0); - if (alloc_size < 1) { - state.SkipWithError("Size for allocation is too small for this test"); - } - for (auto _ : state) { - void * ptr = std::malloc(alloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { - state.SkipWithError("Malloc failed to malloc"); - } - std::free(ptr); - benchmark::DoNotOptimize(ptr); - benchmark::ClobberMemory(); - } -} - -static void benchmark_on_calloc(benchmark::State & state) -{ - const size_t alloc_size = state.range(0); - if (alloc_size < 1) { - state.SkipWithError("Size for allocation is too small for this test"); - } - for (auto _ : state) { - void * ptr = std::calloc(1, alloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { - state.SkipWithError("Calloc failed to calloc"); - } - std::free(ptr); - benchmark::DoNotOptimize(ptr); - benchmark::ClobberMemory(); - } -} - -static void benchmark_on_realloc(benchmark::State & state) -{ - const int64_t malloc_size = state.range(0); - if (malloc_size < 1) { - state.SkipWithError("Size for allocation is too small for this test"); - } - const size_t realloc_size = state.range(1); - if (realloc_size < 1) { - state.SkipWithError("Size for reallocation is too small for this test"); - } - for (auto _ : state) { - void * ptr = std::malloc(malloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == ptr)) { - state.SkipWithError("Malloc failed to malloc"); - } - void * realloc_ptr = std::realloc(ptr, realloc_size); - if (PERFORMANCE_TEST_FIXTURE_UNLIKELY(nullptr == realloc_ptr)) { - std::free(ptr); - state.SkipWithError("Malloc failed to realloc"); - } - std::free(realloc_ptr); - benchmark::DoNotOptimize(realloc_ptr); - benchmark::ClobberMemory(); - } -} - -// Malloc sizes Range from 1 to 2^27 each time multiplying by 16 -BENCHMARK(benchmark_on_malloc)->ArgNames({"Alloc Size"})->RangeMultiplier(16)->Range(1, 1 << 28); - -BENCHMARK(benchmark_on_calloc)->ArgNames({"Alloc Size"})->RangeMultiplier(16)->Range(1, 1 << 28); - -// Three types of realloc tests, one where malloc is smaller than realloc, one where they are -// the same, and one where malloc is larger than realloc. Realloc size ranges from 1 to 2^27 -// each time multiplying by 32. -static void realloc_args(benchmark::internal::Benchmark * b) -{ - for (int64_t malloc_adjustment = -1; malloc_adjustment <= 1; ++malloc_adjustment) { - for (int64_t realloc_shift = 0; realloc_shift < 32; realloc_shift += 8) { - const int64_t malloc_shift = realloc_shift + malloc_adjustment; - if (malloc_shift < 0) { - continue; - } - b->Args({1ll << malloc_shift, 1ll << realloc_shift}); - } - } -} -BENCHMARK(benchmark_on_realloc)->ArgNames({"Alloc Size", "Realloc Size"})->Apply(realloc_args); From aeb68c5739566a95dbdee6aa2f302592d8372bd1 Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Thu, 3 Dec 2020 14:49:54 -0800 Subject: [PATCH 10/10] Some cleanups, make stub failure non-fatal Signed-off-by: Scott K Logan --- .../mimick_memory_manager.hpp | 6 ++++-- src/mimick_memory_manager.cpp | 15 +++++---------- src/mimick_utils.hpp | 11 ++++++----- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/include/performance_test_fixture/mimick_memory_manager.hpp b/include/performance_test_fixture/mimick_memory_manager.hpp index b24f1da..3d077a3 100644 --- a/include/performance_test_fixture/mimick_memory_manager.hpp +++ b/include/performance_test_fixture/mimick_memory_manager.hpp @@ -64,8 +64,10 @@ class MimickMemoryManager : public benchmark::MemoryManager decltype(benchmark::MemoryManager::Result::max_bytes_used) cur_bytes_used; decltype(benchmark::MemoryManager::Result::max_bytes_used) max_bytes_used; decltype(benchmark::MemoryManager::Result::num_allocs) num_allocs; - std::unique_ptr, std::equal_to, - mmk_allocator>> ptr_set; + + template + using mmk_unordered_set = std::unordered_set, std::equal_to, mmk_allocator>; + std::unique_ptr> ptr_set; struct mmk_stub * calloc_stub; struct mmk_stub * free_stub; diff --git a/src/mimick_memory_manager.cpp b/src/mimick_memory_manager.cpp index 9585188..91a25a8 100644 --- a/src/mimick_memory_manager.cpp +++ b/src/mimick_memory_manager.cpp @@ -31,8 +31,7 @@ MimickMemoryManager::MimickMemoryManager() cur_bytes_used(0), max_bytes_used(0), num_allocs(0), - ptr_set(new std::unordered_set, std::equal_to, - mmk_allocator>()), + ptr_set(new mmk_unordered_set()), calloc_stub(MMK_STUB_INVALID), free_stub(MMK_STUB_INVALID), malloc_stub(MMK_STUB_INVALID), @@ -90,26 +89,22 @@ void MimickMemoryManager::Start() calloc_stub = mmk_stub_create_wrapped("calloc", on_calloc, this, size_t, size_t); if (MMK_STUB_INVALID == calloc_stub) { - std::cerr << "Failed to create 'calloc' stub!" << std::endl; - exit(1); + perror("Failed to create 'calloc' stub"); } free_stub = mmk_stub_create_wrapped("free", on_free, this, void *); if (MMK_STUB_INVALID == free_stub) { - std::cerr << "Failed to create 'free' stub!" << std::endl; - exit(1); + perror("Failed to create 'free' stub"); } malloc_stub = mmk_stub_create_wrapped("malloc", on_malloc, this, size_t); if (MMK_STUB_INVALID == malloc_stub) { - std::cerr << "Failed to create 'malloc' stub!" << std::endl; - exit(1); + perror("Failed to create 'malloc' stub"); } realloc_stub = mmk_stub_create_wrapped("realloc", on_realloc, this, void *, size_t); if (MMK_STUB_INVALID == realloc_stub) { - std::cerr << "Failed to create 'realloc' stub!" << std::endl; - exit(1); + perror("Failed to create 'realloc' stub"); } stat_lock.unlock(); diff --git a/src/mimick_utils.hpp b/src/mimick_utils.hpp index 98c9968..7b82ad3 100644 --- a/src/mimick_utils.hpp +++ b/src/mimick_utils.hpp @@ -46,14 +46,15 @@ struct mmk_allocator { typedef T value_type; - mmk_allocator() + mmk_allocator() noexcept { // TODO(cottsay): This is just a hack to get Mimick to initialize the "vital" functions. mmk_stub * stub = mmk_stub_create("free", (mmk_fn)dummy_free, nullptr); - if (MMK_STUB_INVALID == stub) { - abort(); + if (MMK_STUB_INVALID != stub) { + // We don't actually care if this succeeds - calling the function will initialize Mimick + // regardless. + mmk_stub_destroy(stub); } - mmk_stub_destroy(stub); } template @@ -78,7 +79,7 @@ struct mmk_allocator } private: - static void dummy_free(void * unused) + static void dummy_free(void * unused) noexcept { (void)unused; }