aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt9
-rw-r--r--memprof/memprof_stat.cpp163
-rw-r--r--memprof/memprof_stat.h6
-rw-r--r--tdutils/CMakeLists.txt4
-rw-r--r--tdutils/test/hashset_memory.cpp128
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