Skip to content

Commit

Permalink
Initial implementation of a rootbench memory tracing library.
Browse files Browse the repository at this point in the history
It implements a C library which records information about the memory allocations.
The library is loaded using the LD_PRELOAD mechanism (and DYLD_INSERT_LIBRARIES
for osx).

GoogleBenchmark displays the memory allocation results are only in the json
format. For instance:

DYLD_FORCE_FLAT_NAMESPACE=1 DYLD_INSERT_LIBRARIES=./lib/Instrumentation/libRBInstrumentation.dylib ./root/interpreter/InterpreterLookupHelperBenchmarks --benchma
rk_format=json:

...
{
      "name": "BM_LookupHelper_Leak",
      "run_name": "BM_LookupHelper_Leak",
      "run_type": "iteration",
      "repetitions": 0,
      "repetition_index": 0,
      "threads": 1,
      "iterations": 1,
      "real_time": 1.3199219449888916e+09,
      "cpu_time": 8.0450300000000000e+08,
      "time_unit": "ns",
      "allocs_per_iter": 6.0000000000000000e+00,
      "max_bytes_used": 3368
}
  • Loading branch information
vgvassilev committed Oct 12, 2020
1 parent 91213c6 commit 5205fa0
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 12 deletions.
39 changes: 34 additions & 5 deletions cmake/modules/AddRootBench.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function(RB_ADD_GBENCHMARK benchmark)
# FIXME: For better coherence we could restrict the libraries the test suite could link
# against. For example, tests in Core should link only against libCore. This could be tricky
# to implement because some ROOT components create more than one library.
target_link_libraries(${benchmark} ${ARG_LIBRARIES} gbenchmark RBSupport)
target_link_libraries(${benchmark} PUBLIC ${ARG_LIBRARIES} gbenchmark RBSupport)
#ROOT_PATH_TO_STRING(mangled_name ${benchmark} PATH_SEPARATOR_REPLACEMENT "-")
#ROOT_ADD_TEST(gbench${mangled_name}
# COMMAND ${benchmark}
Expand Down Expand Up @@ -76,6 +76,29 @@ function(RB_ADD_GBENCHMARK benchmark)
FIXTURES_REQUIRED "setup-${benchmark};download-${benchmark}-datafiles")
endfunction(RB_ADD_GBENCHMARK)

#----------------------------------------------------------------------------
# function RB_ADD_GBENCHMARK(<benchmark> source1 source2... LIBRARIES libs)
#----------------------------------------------------------------------------
function(RB_ADD_MEM_GBENCHMARK benchmark)
RB_ADD_GBENCHMARK(${benchmark} ${ARGN})

set (benchmark_env)
if(APPLE)
target_link_libraries(${benchmark} PUBLIC
-Wl,-bind_at_load -Wl,-undefined -Wl,dynamic_lookup
)
set (benchmark_env "DYLD_FORCE_FLAT_NAMESPACE=1" "DYLD_INSERT_LIBRARIES=$<TARGET_FILE:RBInstrumentation>")
elseif(NOT MSVC)
target_link_libraries(${benchmark} PUBLIC
-Wl,--unresolved-symbols=ignore-in-object-files
)
set (benchmark_env "LD_PRELOAD=$<TARGET_FILE:RBInstrumentation>")
endif()
if (benchmark_env)
set_property(TEST rootbench-${benchmark} APPEND PROPERTY ENVIRONMENT ${benchmark_env})
endif(benchmark_env)

endfunction(RB_ADD_MEM_GBENCHMARK)

#----------------------------------------------------------------------------
# function RB_ADD_PYTESTBENCHMARK(<benchmark> filename)
Expand Down Expand Up @@ -126,13 +149,19 @@ endfunction()
function(RB_ADD_LIBRARY library)
cmake_parse_arguments(ARG "" "" "LIBRARIES;DEPENDENCIES" ${ARGN})
set(sources ${ARG_UNPARSED_ARGUMENTS})
include_directories(BEFORE ${ROOTBENCH_SOURCE_DIR}/include)
add_library(${library} STATIC ${sources})
if (ARG_LIBRARIES OR ARG_DEPENDENCIES)
target_link_libraries(${library} ${ARG_LIBRARIES} ${ARG_DEPENDENCIES})
endif()
target_include_directories(${library} BEFORE PUBLIC ${ROOTBENCH_SOURCE_DIR}/include)
target_link_libraries(${library} PUBLIC ${ARG_LIBRARIES} ${ARG_DEPENDENCIES} gbenchmark)
endfunction(RB_ADD_LIBRARY)

#----------------------------------------------------------------------------
# function RB_ADD_C_LIBRARY(<library> source1 source2... LIBRARIES libs)
#----------------------------------------------------------------------------
function(RB_ADD_C_LIBRARY library)
RB_ADD_LIBRARY(${library} ${ARGN})
set_target_properties(${library} PROPERTIES LINKER_LANGUAGE C)
endfunction(RB_ADD_C_LIBRARY)

#----------------------------------------------------------------------------
# function RB_ADD_TOOL(<binary> source1 source2... LIBRARIES libs)
#----------------------------------------------------------------------------
Expand Down
24 changes: 24 additions & 0 deletions include/rootbench/Instrumentation/Memory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/// \file Memory.h
///
/// The file contains interfaces of the C memory instrumentation library.
///
/// \author Vassil Vassilev <[email protected]>
///
/// \date Oct, 2020
///

#ifndef RB_MEMORY_H
#define RB_MEMORY_H

#include <stddef.h>

struct malloc_stats {
size_t num_malloc_calls;
size_t max_bytes_used;
};

void reset_malloc_stats();
const struct malloc_stats get_current_malloc_stats();


#endif // RB_MEMORY_H
32 changes: 27 additions & 5 deletions include/rootbench/RBConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
#ifndef RB_CONFIG_H
#define RB_CONFIG_H

#include "rootbench/Support/ErrorHandling.h"
#include <rootbench/Constants.h> // RB::kDatasetDirectory

#include "rootbench/Support/ErrorHandling.h"

#include <string>

#include <stdlib.h>
Expand All @@ -29,16 +30,37 @@ namespace RB {
rb_abort("Please set the ROOTSYS env variable!");
}

constexpr const char* GetPreloadEnvVar() {
#ifdef __APPLE__
// FIXME: Remove the need of DYLD_FORCE_FLAT_NAMESPACE by using interposing.
return "DYLD_INSERT_LIBRARIES";
#elif defined(UNIX)
return "LD_PRELOAD";
#else
# error Unsupported Platform;
#endif
}

/// Checks if we have set up the LD_PRELOAD mechanism for binary
/// instrumentation
inline bool IsLdPreloadEnabled() {
if (char* rootsys = std::getenv(GetPreloadEnvVar()))
return true;
return false;
}

/// Return the absolute path to the directory where data will be downloaded
inline std::string GetDataDir() {
return RB::kDatasetDirectory;
return RB::kDatasetDirectory;
}

/// Like assert, but it does not disappear if -DNDEBUG
inline void Ensure(bool b)
inline void Ensure(bool b, const std::string& reason = "")
{
if (!b)
std::abort();
if (!b) {
printf("Aborting with reason: '%s'\n", reason.c_str());
std::abort();
}
}
}

Expand Down
40 changes: 40 additions & 0 deletions include/rootbench/Support/MemoryManager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/// \file MemoryManager.h
///
/// The file contains the implementation of benchmark memory tracking.
///
/// \author Vassil Vassilev <[email protected]>
///
/// \date Oct, 2020
///

#ifndef RB_MEMORY_MANAGER_H
#define RB_MEMORY_MANAGER_H

#include "benchmark/benchmark.h"


namespace RB {
/// Records number of allocations per iteration and the maximum bites used
/// for a benchmark.
struct MemoryManager : public benchmark::MemoryManager {
size_t cur_num_allocs = 0;
size_t cur_max_bytes_used = 0;
void Start() override;
void Stop(Result* result) override;
};
} // end RB

namespace {
static auto mm = std::unique_ptr<RB::MemoryManager>(new RB::MemoryManager());
static struct InstrumentationRegistrer {
InstrumentationRegistrer() {
benchmark::RegisterMemoryManager(mm.get());
}
~InstrumentationRegistrer() {
benchmark::RegisterMemoryManager(nullptr);
}
} __mem_mgr_register;
}


#endif // RB_MEMORY_MANAGER_H
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
#---Add the support libraries.
add_subdirectory(Instrumentation)
add_subdirectory(Support)
3 changes: 3 additions & 0 deletions lib/Instrumentation/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
RB_ADD_C_LIBRARY(RBInstrumentation SHARED
Memory.c
)
47 changes: 47 additions & 0 deletions lib/Instrumentation/Memory.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// \file Memory.c
///
/// The file contains code of the C memory instrumentation library.
///
/// \author Vassil Vassilev <[email protected]>
///
/// \date Oct, 2020
///


#include "rootbench/Instrumentation/Memory.h"

#define _GNU_SOURCE
#include <stdio.h>
#include <dlfcn.h>
#undef _GNU_SOURCE

static void* (*real_malloc)(size_t) = NULL;

static void mtrace_init(void) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
if (!real_malloc) {
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
}
}

static struct malloc_stats cur_malloc_stats;
void reset_malloc_stats() {
cur_malloc_stats.num_malloc_calls = 0;
cur_malloc_stats.max_bytes_used = 0;
}

const struct malloc_stats get_current_malloc_stats() {
return cur_malloc_stats;
}

void *malloc(size_t size) {
if(!real_malloc)
mtrace_init();

void *p = NULL;
p = real_malloc(size);
cur_malloc_stats.num_malloc_calls++;
cur_malloc_stats.max_bytes_used += size;

return p;
}
7 changes: 6 additions & 1 deletion lib/Support/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
RB_ADD_LIBRARY(RBSupport
ErrorHandling.cxx
)
MemoryManager.cxx
)
target_include_directories(RBSupport PUBLIC
${PROJECT_BINARY_DIR}/include
${PROJECT_SOURCE_DIR}/include
${GBENCHMARK_INCLUDE_DIR})
44 changes: 44 additions & 0 deletions lib/Support/MemoryManager.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// \file MemoryManager.h
///
/// The file contains interfaces for benchmark memory tracking.
///
/// \author Vassil Vassilev <[email protected]>
///
/// \date Oct, 2020
///

#include <rootbench/Support/MemoryManager.h>

#include <rootbench/RBConfig.h>

extern "C" {
#include <rootbench/Instrumentation/Memory.h>
}

#include <benchmark/benchmark.h>

// FIXME: Remove the need of DYLD_FORCE_FLAT_NAMESPACE by using interposing.
static void EnsureFlatNamespaceOSX() {
#ifdef __APPLE__
RB::Ensure(std::getenv("DYLD_FORCE_FLAT_NAMESPACE"),
"DYLD_FORCE_FLAT_NAMESPACE not set.");
#endif // __APPLE__
}

namespace RB {
void MemoryManager::Start() {
Ensure(IsLdPreloadEnabled(), std::string(GetPreloadEnvVar()) + " not set.");
EnsureFlatNamespaceOSX();
cur_num_allocs = 0;
cur_max_bytes_used = 0;
reset_malloc_stats();
}

void MemoryManager::Stop(Result* result) {
Ensure(IsLdPreloadEnabled(), std::string(GetPreloadEnvVar()) + " not set.");
EnsureFlatNamespaceOSX();
const malloc_stats& stats = get_current_malloc_stats();
result->num_allocs = stats.num_malloc_calls;
result->max_bytes_used = stats.max_bytes_used;
}
} // end RB
6 changes: 5 additions & 1 deletion root/interpreter/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ RB_ADD_GBENCHMARK(InterpreterBenchmarks

RB_ADD_GBENCHMARK(InterpreterBenchmarksNoPCH
RunInterpreterNoPCH.cxx
LABEL short)
LABEL short)

RB_ADD_MEM_GBENCHMARK(InterpreterLookupHelperBenchmarks
LookupHelper.cxx LIBRARIES Core
LABEL short)
24 changes: 24 additions & 0 deletions root/interpreter/LookupHelper.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <TInterpreter.h>

#include <rootbench/Support/MemoryManager.h>

#include <benchmark/benchmark.h>


static void BM_LookupHelper_Leak(benchmark::State &state) {
gInterpreter->Declare(R"CODE(
#ifndef BM_LookupHelper_Leak_Guard
#define BM_LookupHelper_Leak_Guard
template <class T, class U> class __gmp_expr;
typedef struct{ } __mpz_struct;
typedef __gmp_expr<__mpz_struct[1],__mpz_struct[1]> mpz_class;
#endif // BM_LookupHelper_Leak_Guard
)CODE");

for (auto _ : state)
gInterpreter->ClassInfo_IsEnum("std::vector<mpz_class>::value_type");
}


BENCHMARK(BM_LookupHelper_Leak);
BENCHMARK_MAIN();

0 comments on commit 5205fa0

Please sign in to comment.