diff options
| -rw-r--r-- | CMakeLists.txt | 9 | ||||
| -rw-r--r-- | memprof/memprof_stat.cpp | 163 | ||||
| -rw-r--r-- | memprof/memprof_stat.h | 6 | ||||
| -rw-r--r-- | tdutils/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | tdutils/test/hashset_memory.cpp | 128 |
5 files changed, 310 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index fb3041ff2..228dc8f7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}>) diff --git a/memprof/memprof_stat.cpp b/memprof/memprof_stat.cpp new file mode 100644 index 000000000..59c352afd --- /dev/null +++ b/memprof/memprof_stat.cpp @@ -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 diff --git a/memprof/memprof_stat.h b/memprof/memprof_stat.h new file mode 100644 index 000000000..d12f9c127 --- /dev/null +++ b/memprof/memprof_stat.h @@ -0,0 +1,6 @@ +#pragma once + +#include <cstddef> + +bool is_memprof_on(); +std::size_t get_used_memory_size(); diff --git a/tdutils/CMakeLists.txt b/tdutils/CMakeLists.txt index aa366628d..cde74c676 100644 --- a/tdutils/CMakeLists.txt +++ b/tdutils/CMakeLists.txt @@ -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() diff --git a/tdutils/test/hashset_memory.cpp b/tdutils/test/hashset_memory.cpp new file mode 100644 index 000000000..eb8fd55bf --- /dev/null +++ b/tdutils/test/hashset_memory.cpp @@ -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; +}
\ No newline at end of file |
