Skip to content

Commit dae8452

Browse files
committed
Initial implementation of a rootbench memory tracing library.
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 }
1 parent 91213c6 commit dae8452

File tree

11 files changed

+255
-12
lines changed

11 files changed

+255
-12
lines changed

cmake/modules/AddRootBench.cmake

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function(RB_ADD_GBENCHMARK benchmark)
4343
# FIXME: For better coherence we could restrict the libraries the test suite could link
4444
# against. For example, tests in Core should link only against libCore. This could be tricky
4545
# to implement because some ROOT components create more than one library.
46-
target_link_libraries(${benchmark} ${ARG_LIBRARIES} gbenchmark RBSupport)
46+
target_link_libraries(${benchmark} PUBLIC ${ARG_LIBRARIES} gbenchmark RBSupport)
4747
#ROOT_PATH_TO_STRING(mangled_name ${benchmark} PATH_SEPARATOR_REPLACEMENT "-")
4848
#ROOT_ADD_TEST(gbench${mangled_name}
4949
# COMMAND ${benchmark}
@@ -76,6 +76,29 @@ function(RB_ADD_GBENCHMARK benchmark)
7676
FIXTURES_REQUIRED "setup-${benchmark};download-${benchmark}-datafiles")
7777
endfunction(RB_ADD_GBENCHMARK)
7878

79+
#----------------------------------------------------------------------------
80+
# function RB_ADD_GBENCHMARK(<benchmark> source1 source2... LIBRARIES libs)
81+
#----------------------------------------------------------------------------
82+
function(RB_ADD_MEM_GBENCHMARK benchmark)
83+
RB_ADD_GBENCHMARK(${benchmark} ${ARGN})
84+
85+
set (benchmark_env)
86+
if(APPLE)
87+
target_link_libraries(${benchmark} PUBLIC
88+
-Wl,-bind_at_load -Wl,-undefined -Wl,dynamic_lookup
89+
)
90+
set (benchmark_env "DYLD_FORCE_FLAT_NAMESPACE=1" "DYLD_INSERT_LIBRARIES=$<TARGET_FILE:RBInstrumentation>")
91+
elseif(NOT MSVC)
92+
target_link_libraries(${benchmark} PUBLIC
93+
-Wl,--unresolved-symbols=ignore-in-object-files
94+
)
95+
set (benchmark_env "LD_PRELOAD=$<TARGET_FILE:RBInstrumentation>")
96+
endif()
97+
if (benchmark_env)
98+
set_property(TEST rootbench-${benchmark} APPEND PROPERTY ENVIRONMENT ${benchmark_env})
99+
endif(benchmark_env)
100+
101+
endfunction(RB_ADD_MEM_GBENCHMARK)
79102

80103
#----------------------------------------------------------------------------
81104
# function RB_ADD_PYTESTBENCHMARK(<benchmark> filename)
@@ -126,13 +149,19 @@ endfunction()
126149
function(RB_ADD_LIBRARY library)
127150
cmake_parse_arguments(ARG "" "" "LIBRARIES;DEPENDENCIES" ${ARGN})
128151
set(sources ${ARG_UNPARSED_ARGUMENTS})
129-
include_directories(BEFORE ${ROOTBENCH_SOURCE_DIR}/include)
130152
add_library(${library} STATIC ${sources})
131-
if (ARG_LIBRARIES OR ARG_DEPENDENCIES)
132-
target_link_libraries(${library} ${ARG_LIBRARIES} ${ARG_DEPENDENCIES})
133-
endif()
153+
target_include_directories(${library} BEFORE PUBLIC ${ROOTBENCH_SOURCE_DIR}/include)
154+
target_link_libraries(${library} PUBLIC ${ARG_LIBRARIES} ${ARG_DEPENDENCIES} gbenchmark)
134155
endfunction(RB_ADD_LIBRARY)
135156

157+
#----------------------------------------------------------------------------
158+
# function RB_ADD_C_LIBRARY(<library> source1 source2... LIBRARIES libs)
159+
#----------------------------------------------------------------------------
160+
function(RB_ADD_C_LIBRARY library)
161+
RB_ADD_LIBRARY(${library} ${ARGN})
162+
set_target_properties(${library} PROPERTIES LINKER_LANGUAGE C)
163+
endfunction(RB_ADD_C_LIBRARY)
164+
136165
#----------------------------------------------------------------------------
137166
# function RB_ADD_TOOL(<binary> source1 source2... LIBRARIES libs)
138167
#----------------------------------------------------------------------------
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// \file Memory.h
2+
///
3+
/// The file contains interfaces of the C memory instrumentation library.
4+
///
5+
/// \author Vassil Vassilev <[email protected]>
6+
///
7+
/// \date Oct, 2020
8+
///
9+
10+
#ifndef RB_MEMORY_H
11+
#define RB_MEMORY_H
12+
13+
#include <stddef.h>
14+
15+
struct malloc_stats {
16+
size_t num_malloc_calls;
17+
size_t max_bytes_used;
18+
};
19+
20+
void reset_malloc_stats();
21+
const struct malloc_stats get_current_malloc_stats();
22+
23+
24+
#endif // RB_MEMORY_H

include/rootbench/RBConfig.h

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
#ifndef RB_CONFIG_H
33
#define RB_CONFIG_H
44

5-
#include "rootbench/Support/ErrorHandling.h"
65
#include <rootbench/Constants.h> // RB::kDatasetDirectory
76

7+
#include "rootbench/Support/ErrorHandling.h"
8+
89
#include <string>
910

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

33+
constexpr const char* GetPreloadEnvVar() {
34+
#ifdef __APPLE__
35+
// FIXME: Remove the need of DYLD_FORCE_FLAT_NAMESPACE by using interposing.
36+
return "DYLD_INSERT_LIBRARIES";
37+
#elif defined(UNIX)
38+
return "LD_PRELOAD";
39+
#else
40+
# error Unsupported Platform;
41+
#endif
42+
}
43+
44+
/// Checks if we have set up the LD_PRELOAD mechanism for binary
45+
/// instrumentation
46+
inline bool IsLdPreloadEnabled() {
47+
if (char* rootsys = std::getenv(GetPreloadEnvVar()))
48+
return true;
49+
return false;
50+
}
51+
3252
/// Return the absolute path to the directory where data will be downloaded
3353
inline std::string GetDataDir() {
34-
return RB::kDatasetDirectory;
54+
return RB::kDatasetDirectory;
3555
}
3656

3757
/// Like assert, but it does not disappear if -DNDEBUG
38-
inline void Ensure(bool b)
58+
inline void Ensure(bool b, const std::string& reason = "")
3959
{
40-
if (!b)
41-
std::abort();
60+
if (!b) {
61+
printf("Aborting with reason: '%s'\n", reason.c_str());
62+
std::abort();
63+
}
4264
}
4365
}
4466

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/// \file MemoryManager.h
2+
///
3+
/// The file contains the implementation of benchmark memory tracking.
4+
///
5+
/// \author Vassil Vassilev <[email protected]>
6+
///
7+
/// \date Oct, 2020
8+
///
9+
10+
#ifndef RB_MEMORY_MANAGER_H
11+
#define RB_MEMORY_MANAGER_H
12+
13+
#include "benchmark/benchmark.h"
14+
15+
16+
namespace RB {
17+
/// Records number of allocations per iteration and the maximum bites used
18+
/// for a benchmark.
19+
struct MemoryManager : public benchmark::MemoryManager {
20+
size_t cur_num_allocs = 0;
21+
size_t cur_max_bytes_used = 0;
22+
void Start() override;
23+
void Stop(Result* result) override;
24+
};
25+
} // end RB
26+
27+
namespace {
28+
static auto mm = std::unique_ptr<RB::MemoryManager>(new RB::MemoryManager());
29+
static struct InstrumentationRegistrer {
30+
InstrumentationRegistrer() {
31+
benchmark::RegisterMemoryManager(mm.get());
32+
}
33+
~InstrumentationRegistrer() {
34+
benchmark::RegisterMemoryManager(nullptr);
35+
}
36+
} __mem_mgr_register;
37+
}
38+
39+
40+
#endif // RB_MEMORY_MANAGER_H

lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
#---Add the support libraries.
2+
add_subdirectory(Instrumentation)
23
add_subdirectory(Support)

lib/Instrumentation/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RB_ADD_C_LIBRARY(RBInstrumentation SHARED
2+
Memory.c
3+
)

lib/Instrumentation/Memory.c

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/// \file Memory.c
2+
///
3+
/// The file contains code of the C memory instrumentation library.
4+
///
5+
/// \author Vassil Vassilev <[email protected]>
6+
///
7+
/// \date Oct, 2020
8+
///
9+
10+
11+
#include "rootbench/Instrumentation/Memory.h"
12+
13+
#define _GNU_SOURCE
14+
#include <stdio.h>
15+
#include <dlfcn.h>
16+
#undef _GNU_SOURCE
17+
18+
static void* (*real_malloc)(size_t) = NULL;
19+
20+
static void mtrace_init(void) {
21+
real_malloc = dlsym(RTLD_NEXT, "malloc");
22+
if (!real_malloc) {
23+
fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());
24+
}
25+
}
26+
27+
static struct malloc_stats cur_malloc_stats;
28+
void reset_malloc_stats() {
29+
cur_malloc_stats.num_malloc_calls = 0;
30+
cur_malloc_stats.max_bytes_used = 0;
31+
}
32+
33+
const struct malloc_stats get_current_malloc_stats() {
34+
return cur_malloc_stats;
35+
}
36+
37+
void *malloc(size_t size) {
38+
if(!real_malloc)
39+
mtrace_init();
40+
41+
void *p = NULL;
42+
p = real_malloc(size);
43+
cur_malloc_stats.num_malloc_calls++;
44+
cur_malloc_stats.max_bytes_used += size;
45+
46+
return p;
47+
}

lib/Support/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
RB_ADD_LIBRARY(RBSupport
22
ErrorHandling.cxx
3-
)
3+
MemoryManager.cxx
4+
)
5+
target_include_directories(RBSupport PUBLIC
6+
${PROJECT_BINARY_DIR}/include
7+
${PROJECT_SOURCE_DIR}/include
8+
${GBENCHMARK_INCLUDE_DIR})

lib/Support/MemoryManager.cxx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/// \file MemoryManager.h
2+
///
3+
/// The file contains interfaces for benchmark memory tracking.
4+
///
5+
/// \author Vassil Vassilev <[email protected]>
6+
///
7+
/// \date Oct, 2020
8+
///
9+
10+
#include <rootbench/Support/MemoryManager.h>
11+
12+
#include <rootbench/RBConfig.h>
13+
14+
extern "C" {
15+
#include <rootbench/Instrumentation/Memory.h>
16+
}
17+
18+
#include <benchmark/benchmark.h>
19+
20+
// FIXME: Remove the need of DYLD_FORCE_FLAT_NAMESPACE by using interposing.
21+
static void EnsureFlatNamespaceOSX() {
22+
#ifdef __APPLE__
23+
RB::Ensure(std::getenv("DYLD_FORCE_FLAT_NAMESPACE"),
24+
"DYLD_FORCE_FLAT_NAMESPACE not set.");
25+
#endif // __APPLE__
26+
}
27+
28+
namespace RB {
29+
void MemoryManager::Start() {
30+
Ensure(IsLdPreloadEnabled(), std::string(GetPreloadEnvVar()) + " not set.");
31+
EnsureFlatNamespaceOSX();
32+
cur_num_allocs = 0;
33+
cur_max_bytes_used = 0;
34+
reset_malloc_stats();
35+
}
36+
37+
void MemoryManager::Stop(Result* result) {
38+
Ensure(IsLdPreloadEnabled(), std::string(GetPreloadEnvVar()) + " not set.");
39+
EnsureFlatNamespaceOSX();
40+
const malloc_stats& stats = get_current_malloc_stats();
41+
result->num_allocs = stats.num_malloc_calls;
42+
result->max_bytes_used = stats.max_bytes_used;
43+
}
44+
} // end RB

root/interpreter/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,8 @@ RB_ADD_GBENCHMARK(InterpreterBenchmarks
44

55
RB_ADD_GBENCHMARK(InterpreterBenchmarksNoPCH
66
RunInterpreterNoPCH.cxx
7-
LABEL short)
7+
LABEL short)
8+
9+
RB_ADD_MEM_GBENCHMARK(InterpreterLookupHelperBenchmarks
10+
LookupHelper.cxx LIBRARIES Core
11+
LABEL short)

root/interpreter/LookupHelper.cxx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include <TInterpreter.h>
2+
3+
#include <rootbench/Support/MemoryManager.h>
4+
5+
#include <benchmark/benchmark.h>
6+
7+
8+
static void BM_LookupHelper_Leak(benchmark::State &state) {
9+
gInterpreter->Declare(R"CODE(
10+
#ifndef BM_LookupHelper_Leak_Guard
11+
#define BM_LookupHelper_Leak_Guard
12+
template <class T, class U> class __gmp_expr;
13+
typedef struct{ } __mpz_struct;
14+
typedef __gmp_expr<__mpz_struct[1],__mpz_struct[1]> mpz_class;
15+
#endif // BM_LookupHelper_Leak_Guard
16+
)CODE");
17+
18+
for (auto _ : state)
19+
gInterpreter->ClassInfo_IsEnum("std::vector<mpz_class>::value_type");
20+
}
21+
22+
23+
BENCHMARK(BM_LookupHelper_Leak);
24+
BENCHMARK_MAIN();

0 commit comments

Comments
 (0)