FlatHashMap: simple benchmark for memory usage
This commit is contained in:
parent
f4b3a09646
commit
10c59db842
@ -706,6 +706,11 @@ set(MEMPROF_SOURCE
|
||||
memprof/memprof.h
|
||||
)
|
||||
|
||||
set(MEMPROF_STAT_SOURCE
|
||||
memprof/memprof_stat.cpp
|
||||
memprof/memprof_stat.h
|
||||
)
|
||||
|
||||
#RULES
|
||||
|
||||
file(MAKE_DIRECTORY auto)
|
||||
@ -739,6 +744,10 @@ if (MEMPROF)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(memprof_stat STATIC ${MEMPROF_STAT_SOURCE})
|
||||
target_include_directories(memprof_stat PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
|
||||
target_link_libraries(memprof_stat PRIVATE tdutils)
|
||||
|
||||
|
||||
add_library(tdapi ${TL_TD_API_SOURCE})
|
||||
target_include_directories(tdapi PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> INTERFACE $<BUILD_INTERFACE:${TL_TD_AUTO_INCLUDE_DIR}>)
|
||||
|
163
memprof/memprof_stat.cpp
Normal file
163
memprof/memprof_stat.cpp
Normal file
@ -0,0 +1,163 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
#include "memprof_stat.h"
|
||||
|
||||
#include "td/utils/port/platform.h"
|
||||
|
||||
#if (TD_DARWIN || TD_LINUX)
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <execinfo.h>
|
||||
|
||||
bool is_memprof_on() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#define my_assert(f) \
|
||||
if (!(f)) { \
|
||||
std::abort(); \
|
||||
}
|
||||
|
||||
struct malloc_info {
|
||||
std::int32_t magic;
|
||||
std::int32_t size;
|
||||
};
|
||||
|
||||
std::atomic<uint64_t> total_memory_used;
|
||||
void register_xalloc(malloc_info *info, std::int32_t diff) {
|
||||
my_assert(info->size >= 0);
|
||||
// TODO: this is very slow in case of several threads.
|
||||
// Currently this statistics is intended only for memory benchmarks.
|
||||
total_memory_used.fetch_add(diff * info->size, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
std::size_t get_used_memory_size() {
|
||||
return total_memory_used.load();
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
static constexpr std::size_t RESERVED_SIZE = 16;
|
||||
static constexpr std::int32_t MALLOC_INFO_MAGIC = 0x27138373;
|
||||
|
||||
static void *do_malloc(std::size_t size) {
|
||||
static_assert(RESERVED_SIZE % alignof(std::max_align_t) == 0, "fail");
|
||||
static_assert(RESERVED_SIZE >= sizeof(malloc_info), "fail");
|
||||
#if TD_DARWIN
|
||||
static void *malloc_void = dlsym(RTLD_NEXT, "malloc");
|
||||
static auto malloc_old = *reinterpret_cast<decltype(malloc) **>(&malloc_void);
|
||||
#else
|
||||
extern decltype(malloc) __libc_malloc;
|
||||
static auto malloc_old = __libc_malloc;
|
||||
#endif
|
||||
auto *info = static_cast<malloc_info *>(malloc_old(size + RESERVED_SIZE));
|
||||
auto *buf = reinterpret_cast<char *>(info);
|
||||
|
||||
info->magic = MALLOC_INFO_MAGIC;
|
||||
info->size = static_cast<std::int32_t>(size);
|
||||
|
||||
register_xalloc(info, +1);
|
||||
|
||||
void *data = buf + RESERVED_SIZE;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static malloc_info *get_info(void *data_void) {
|
||||
auto *data = static_cast<char *>(data_void);
|
||||
auto *buf = data - RESERVED_SIZE;
|
||||
|
||||
auto *info = reinterpret_cast<malloc_info *>(buf);
|
||||
my_assert(info->magic == MALLOC_INFO_MAGIC);
|
||||
return info;
|
||||
}
|
||||
|
||||
void *malloc(std::size_t size) {
|
||||
return do_malloc(size);
|
||||
}
|
||||
|
||||
void free(void *data_void) {
|
||||
if (data_void == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto *info = get_info(data_void);
|
||||
register_xalloc(info, -1);
|
||||
|
||||
#if TD_DARWIN
|
||||
static void *free_void = dlsym(RTLD_NEXT, "free");
|
||||
static auto free_old = *reinterpret_cast<decltype(free) **>(&free_void);
|
||||
#else
|
||||
extern decltype(free) __libc_free;
|
||||
static auto free_old = __libc_free;
|
||||
#endif
|
||||
return free_old(info);
|
||||
}
|
||||
void *calloc(std::size_t size_a, std::size_t size_b) {
|
||||
auto size = size_a * size_b;
|
||||
void *res = do_malloc(size);
|
||||
std::memset(res, 0, size);
|
||||
return res;
|
||||
}
|
||||
void *realloc(void *ptr, std::size_t size) {
|
||||
if (ptr == nullptr) {
|
||||
return do_malloc(size);
|
||||
}
|
||||
auto *info = get_info(ptr);
|
||||
auto *new_ptr = do_malloc(size);
|
||||
auto to_copy = std::min(static_cast<std::int32_t>(size), info->size);
|
||||
std::memcpy(new_ptr, ptr, to_copy);
|
||||
free(ptr);
|
||||
return new_ptr;
|
||||
}
|
||||
void *memalign(std::size_t alignment, std::size_t size) {
|
||||
auto res = malloc(size);
|
||||
my_assert(reinterpret_cast<uint64_t>(res) % alignment == 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
int posix_memalign(void **memptr, size_t alignment, size_t size) {
|
||||
auto res = malloc(size);
|
||||
my_assert(reinterpret_cast<uint64_t>(res) % alignment == 0);
|
||||
*memptr = res;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// c++14 guarantees that it is enough to override these two operators.
|
||||
void *operator new(std::size_t count) {
|
||||
return do_malloc(count);
|
||||
}
|
||||
void operator delete(void *ptr) noexcept(true) {
|
||||
free(ptr);
|
||||
}
|
||||
// because of gcc warning: the program should also define 'void operator delete(void*, std::size_t)'
|
||||
void operator delete(void *ptr, std::size_t) noexcept(true) {
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
// c++17
|
||||
// void *operator new(std::size_t count, std::align_val_t al);
|
||||
// void operator delete(void *ptr, std::align_val_t al);
|
||||
|
||||
#else
|
||||
bool is_memprof_on() {
|
||||
return false;
|
||||
}
|
||||
std::size_t get_used_memory_size() {
|
||||
return false;
|
||||
}
|
||||
#endif
|
6
memprof/memprof_stat.h
Normal file
6
memprof/memprof_stat.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
bool is_memprof_on();
|
||||
std::size_t get_used_memory_size();
|
@ -395,4 +395,8 @@ if (ABSL_FOUND AND benchmark_FOUND AND gflags_FOUND AND folly_FOUND)
|
||||
add_executable(benchmark-hashset ${CMAKE_CURRENT_SOURCE_DIR}/test/hashset_benchmark.cpp)
|
||||
target_link_libraries(benchmark-hashset PRIVATE tdutils)
|
||||
target_link_libraries(benchmark-hashset PRIVATE SYSTEM benchmark::benchmark Folly::folly absl::flat_hash_map absl::hash)
|
||||
|
||||
add_executable(memory-hashset ${CMAKE_CURRENT_SOURCE_DIR}/test/hashset_memory.cpp)
|
||||
target_link_libraries(memory-hashset PRIVATE tdutils memprof_stat)
|
||||
target_link_libraries(memory-hashset PRIVATE SYSTEM Folly::folly absl::flat_hash_map absl::hash)
|
||||
endif()
|
||||
|
128
tdutils/test/hashset_memory.cpp
Normal file
128
tdutils/test/hashset_memory.cpp
Normal file
@ -0,0 +1,128 @@
|
||||
//
|
||||
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
//
|
||||
|
||||
#include "memprof/memprof.h"
|
||||
#include "td/utils/check.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/FlatHashMap.h"
|
||||
#include "td/utils/format.h"
|
||||
#include "td/utils/UInt.h"
|
||||
|
||||
#include <folly/container/F14Map.h>
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
template <class T>
|
||||
class Generator {
|
||||
public:
|
||||
T next() {
|
||||
UNREACHABLE();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class IntGenerator {
|
||||
public:
|
||||
T next() {
|
||||
return ++value;
|
||||
}
|
||||
private:
|
||||
T value{};
|
||||
};
|
||||
|
||||
template <>
|
||||
class Generator<uint32_t> : public IntGenerator<uint32_t> {
|
||||
public:
|
||||
};
|
||||
template <>
|
||||
class Generator<uint64_t> : public IntGenerator<uint64_t> {
|
||||
public:
|
||||
};
|
||||
|
||||
template <class T, class KeyT, class ValueT>
|
||||
void measure(td::StringBuilder &sb, td::Slice name, td::Slice key_name, td::Slice value_name) {
|
||||
sb << name << "<" << key_name << "," << value_name << ">:\n";
|
||||
size_t ideal_size = sizeof(KeyT) + sizeof(ValueT);
|
||||
|
||||
sb << "\tempty:" << sizeof(T);
|
||||
struct Stat {
|
||||
int pi;
|
||||
double min_ratio;
|
||||
double max_ratio;
|
||||
};
|
||||
std::vector<Stat> stat;
|
||||
stat.reserve(1024);
|
||||
for (size_t size : {10000000u}) {
|
||||
Generator<KeyT> key_generator;
|
||||
auto start_mem = get_used_memory_size();
|
||||
T ht;
|
||||
auto ratio = [&]() {
|
||||
auto end_mem = get_used_memory_size();
|
||||
auto used_mem = end_mem - start_mem;
|
||||
return double(used_mem) / double(ideal_size * ht.size());
|
||||
};
|
||||
double min_ratio;
|
||||
double max_ratio;
|
||||
auto reset = [&]() {
|
||||
min_ratio = 1e100;
|
||||
max_ratio = 0;
|
||||
};
|
||||
auto update = [&]() {
|
||||
auto x = ratio();
|
||||
min_ratio = std::min(min_ratio, x);
|
||||
max_ratio = std::max(max_ratio, x);
|
||||
if (x > 14) {
|
||||
LOG(ERROR) << "WTF";
|
||||
}
|
||||
};
|
||||
reset();
|
||||
|
||||
int p = 10;
|
||||
int pi = 1;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
ht.emplace(key_generator.next(), ValueT{});
|
||||
update();
|
||||
if ((i + 1) % p == 0) {
|
||||
stat.emplace_back(Stat{pi, min_ratio, max_ratio});
|
||||
reset();
|
||||
pi++;
|
||||
p *= 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &s : stat) {
|
||||
sb << " " << 10 << "^" << s.pi << ":" << s.min_ratio << "->" << s.max_ratio;
|
||||
}
|
||||
sb << "\n";
|
||||
}
|
||||
|
||||
|
||||
template <template<typename... Args> class T>
|
||||
void print_memory_stats(td::Slice name) {
|
||||
std::string big_buff(1<<16, '\0');
|
||||
td::StringBuilder sb(big_buff, false);
|
||||
#define MEASURE(KeyT, ValueT) \
|
||||
measure<T<KeyT, ValueT>, KeyT, ValueT>(sb, name, #KeyT, #ValueT);
|
||||
MEASURE(uint32_t, uint32_t)
|
||||
// MEASURE(uint64_t, td::UInt256)
|
||||
LOG(ERROR) << "\n" << sb.as_cslice();
|
||||
}
|
||||
|
||||
#define FOR_EACH_TABLE(F) \
|
||||
F(td::FlatHashMapImpl) \
|
||||
F(folly::F14FastMap) \
|
||||
F(absl::flat_hash_map) \
|
||||
F(std::unordered_map) \
|
||||
F(std::map)
|
||||
#define BENCH_MEMORY(T) print_memory_stats<T>(#T);
|
||||
|
||||
int main() {
|
||||
CHECK(get_used_memory_size());
|
||||
FOR_EACH_TABLE(BENCH_MEMORY);
|
||||
return 0;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user