// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_ #define BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "base/base_export.h" #include "base/containers/circular_deque.h" #include "base/containers/flat_map.h" #include "base/containers/flat_set.h" #include "base/containers/linked_list.h" #include "base/containers/queue.h" #include "base/strings/string16.h" // Composable memory usage estimators. // // This file defines set of EstimateMemoryUsage(object) functions that return // approximate memory usage of their argument. // // The ultimate goal is to make memory usage estimation for a class simply a // matter of aggregating EstimateMemoryUsage() results over all fields. // // That is achieved via composability: if EstimateMemoryUsage() is defined // for T then EstimateMemoryUsage() is also defined for any combination of // containers holding T (e.g. std::map>). // // There are two ways of defining EstimateMemoryUsage() for a type: // // 1. As a global function 'size_t EstimateMemoryUsage(T)' in // in base::trace_event namespace. // // 2. As 'size_t T::EstimateMemoryUsage() const' method. In this case // EstimateMemoryUsage(T) function in base::trace_event namespace is // provided automatically. // // Here is an example implementation: // // size_t foo::bar::MyClass::EstimateMemoryUsage() const { // return base::trace_event::EstimateMemoryUsage(name_) + // base::trace_event::EstimateMemoryUsage(id_) + // base::trace_event::EstimateMemoryUsage(items_); // } // // The approach is simple: first call EstimateMemoryUsage() on all members, // then recursively fix compilation errors that are caused by types not // implementing EstimateMemoryUsage(). namespace base { namespace trace_event { // Declarations // If T declares 'EstimateMemoryUsage() const' member function, then // global function EstimateMemoryUsage(T) is available, and just calls // the member function. template auto EstimateMemoryUsage(const T& object) -> decltype(object.EstimateMemoryUsage()); // String template size_t EstimateMemoryUsage(const std::basic_string& string); // Arrays template size_t EstimateMemoryUsage(const std::array& array); template size_t EstimateMemoryUsage(T (&array)[N]); template size_t EstimateMemoryUsage(const T* array, size_t array_length); // std::unique_ptr template size_t EstimateMemoryUsage(const std::unique_ptr& ptr); template size_t EstimateMemoryUsage(const std::unique_ptr& array, size_t array_length); // std::shared_ptr template size_t EstimateMemoryUsage(const std::shared_ptr& ptr); // Containers template size_t EstimateMemoryUsage(const std::pair& pair); template size_t EstimateMemoryUsage(const std::vector& vector); template size_t EstimateMemoryUsage(const std::list& list); template size_t EstimateMemoryUsage(const base::LinkedList& list); template size_t EstimateMemoryUsage(const std::set& set); template size_t EstimateMemoryUsage(const std::multiset& set); template size_t EstimateMemoryUsage(const std::map& map); template size_t EstimateMemoryUsage(const std::multimap& map); template size_t EstimateMemoryUsage(const std::unordered_set& set); template size_t EstimateMemoryUsage(const std::unordered_multiset& set); template size_t EstimateMemoryUsage(const std::unordered_map& map); template size_t EstimateMemoryUsage(const std::unordered_multimap& map); template size_t EstimateMemoryUsage(const std::deque& deque); template size_t EstimateMemoryUsage(const std::queue& queue); template size_t EstimateMemoryUsage(const std::priority_queue& queue); template size_t EstimateMemoryUsage(const std::stack& stack); template size_t EstimateMemoryUsage(const base::circular_deque& deque); template size_t EstimateMemoryUsage(const base::flat_set& set); template size_t EstimateMemoryUsage(const base::flat_map& map); // TODO(dskiba): // std::forward_list // Definitions namespace internal { // HasEMU::value is true iff EstimateMemoryUsage(T) is available. // (This is the default version, which is false.) template struct HasEMU : std::false_type {}; // This HasEMU specialization is only picked up if there exists function // EstimateMemoryUsage(const T&) that returns size_t. Simpler ways to // achieve this don't work on MSVC. template struct HasEMU< T, typename std::enable_if()))>::value>::type> : std::true_type {}; // EMUCaller does three things: // 1. Defines Call() method that calls EstimateMemoryUsage(T) if it's // available. // 2. If EstimateMemoryUsage(T) is not available, but T has trivial dtor // (i.e. it's POD, integer, pointer, enum, etc.) then it defines Call() // method that returns 0. This is useful for containers, which allocate // memory regardless of T (also for cases like std::map). // 3. Finally, if EstimateMemoryUsage(T) is not available, then it triggers // a static_assert with a helpful message. That cuts numbers of errors // considerably - if you just call EstimateMemoryUsage(T) but it's not // available for T, then compiler will helpfully list *all* possible // variants of it, with an explanation for each. template struct EMUCaller { // std::is_same<> below makes static_assert depend on T, in order to // prevent it from asserting regardless instantiation. static_assert(std::is_same::value, "Neither global function 'size_t EstimateMemoryUsage(T)' " "nor member function 'size_t T::EstimateMemoryUsage() const' " "is defined for the type."); static size_t Call(const T&) { return 0; } }; template struct EMUCaller::value>::type> { static size_t Call(const T& value) { return EstimateMemoryUsage(value); } }; template struct EMUCaller< T, typename std::enable_if::value && std::is_trivially_destructible::value>::type> { static size_t Call(const T& value) { return 0; } }; // Returns reference to the underlying container of a container adapter. // Works for std::stack, std::queue and std::priority_queue. template const typename A::container_type& GetUnderlyingContainer(const A& adapter) { struct ExposedAdapter : A { using A::c; }; return adapter.*&ExposedAdapter::c; } } // namespace internal // Proxy that deducts T and calls EMUCaller. // To be used by EstimateMemoryUsage() implementations for containers. template size_t EstimateItemMemoryUsage(const T& value) { return internal::EMUCaller::Call(value); } template size_t EstimateIterableMemoryUsage(const I& iterable) { size_t memory_usage = 0; for (const auto& item : iterable) { memory_usage += EstimateItemMemoryUsage(item); } return memory_usage; } // Global EstimateMemoryUsage(T) that just calls T::EstimateMemoryUsage(). template auto EstimateMemoryUsage(const T& object) -> decltype(object.EstimateMemoryUsage()) { static_assert( std::is_same::value, "'T::EstimateMemoryUsage() const' must return size_t."); return object.EstimateMemoryUsage(); } // String template size_t EstimateMemoryUsage(const std::basic_string& string) { using string_type = std::basic_string; using value_type = typename string_type::value_type; // C++11 doesn't leave much room for implementors - std::string can // use short string optimization, but that's about it. We detect SSO // by checking that c_str() points inside |string|. const uint8_t* cstr = reinterpret_cast(string.c_str()); const uint8_t* inline_cstr = reinterpret_cast(&string); if (cstr >= inline_cstr && cstr < inline_cstr + sizeof(string)) { // SSO string return 0; } return (string.capacity() + 1) * sizeof(value_type); } // Use explicit instantiations from the .cc file (reduces bloat). extern template BASE_EXPORT size_t EstimateMemoryUsage(const std::string&); extern template BASE_EXPORT size_t EstimateMemoryUsage(const string16&); // Arrays template size_t EstimateMemoryUsage(const std::array& array) { return EstimateIterableMemoryUsage(array); } template size_t EstimateMemoryUsage(T (&array)[N]) { return EstimateIterableMemoryUsage(array); } template size_t EstimateMemoryUsage(const T* array, size_t array_length) { size_t memory_usage = sizeof(T) * array_length; for (size_t i = 0; i != array_length; ++i) { memory_usage += EstimateItemMemoryUsage(array[i]); } return memory_usage; } // std::unique_ptr template size_t EstimateMemoryUsage(const std::unique_ptr& ptr) { return ptr ? (sizeof(T) + EstimateItemMemoryUsage(*ptr)) : 0; } template size_t EstimateMemoryUsage(const std::unique_ptr& array, size_t array_length) { return EstimateMemoryUsage(array.get(), array_length); } // std::shared_ptr template size_t EstimateMemoryUsage(const std::shared_ptr& ptr) { auto use_count = ptr.use_count(); if (use_count == 0) { return 0; } // Model shared_ptr after libc++, // see __shared_ptr_pointer from include/memory struct SharedPointer { void* vtbl; long shared_owners; long shared_weak_owners; T* value; }; // If object of size S shared N > S times we prefer to (potentially) // overestimate than to return 0. return sizeof(SharedPointer) + (EstimateItemMemoryUsage(*ptr) + (use_count - 1)) / use_count; } // std::pair template size_t EstimateMemoryUsage(const std::pair& pair) { return EstimateItemMemoryUsage(pair.first) + EstimateItemMemoryUsage(pair.second); } // std::vector template size_t EstimateMemoryUsage(const std::vector& vector) { return sizeof(T) * vector.capacity() + EstimateIterableMemoryUsage(vector); } // std::list template size_t EstimateMemoryUsage(const std::list& list) { using value_type = typename std::list::value_type; struct Node { Node* prev; Node* next; value_type value; }; return sizeof(Node) * list.size() + EstimateIterableMemoryUsage(list); } template size_t EstimateMemoryUsage(const base::LinkedList& list) { size_t memory_usage = 0u; for (base::LinkNode* node = list.head(); node != list.end(); node = node->next()) { // Since we increment by calling node = node->next() we know that node // isn't nullptr. memory_usage += EstimateMemoryUsage(*node->value()) + sizeof(T); } return memory_usage; } // Tree containers template size_t EstimateTreeMemoryUsage(size_t size) { // Tree containers are modeled after libc++ // (__tree_node from include/__tree) struct Node { Node* left; Node* right; Node* parent; bool is_black; V value; }; return sizeof(Node) * size; } template size_t EstimateMemoryUsage(const std::set& set) { using value_type = typename std::set::value_type; return EstimateTreeMemoryUsage(set.size()) + EstimateIterableMemoryUsage(set); } template size_t EstimateMemoryUsage(const std::multiset& set) { using value_type = typename std::multiset::value_type; return EstimateTreeMemoryUsage(set.size()) + EstimateIterableMemoryUsage(set); } template size_t EstimateMemoryUsage(const std::map& map) { using value_type = typename std::map::value_type; return EstimateTreeMemoryUsage(map.size()) + EstimateIterableMemoryUsage(map); } template size_t EstimateMemoryUsage(const std::multimap& map) { using value_type = typename std::multimap::value_type; return EstimateTreeMemoryUsage(map.size()) + EstimateIterableMemoryUsage(map); } // HashMap containers namespace internal { // While hashtable containers model doesn't depend on STL implementation, one // detail still crept in: bucket_count. It's used in size estimation, but its // value after inserting N items is not predictable. // This function is specialized by unittests to return constant value, thus // excluding bucket_count from testing. template size_t HashMapBucketCountForTesting(size_t bucket_count) { return bucket_count; } } // namespace internal template size_t EstimateHashMapMemoryUsage(size_t bucket_count, size_t size) { // Hashtable containers are modeled after libc++ // (__hash_node from include/__hash_table) struct Node { void* next; size_t hash; V value; }; using Bucket = void*; bucket_count = internal::HashMapBucketCountForTesting(bucket_count); return sizeof(Bucket) * bucket_count + sizeof(Node) * size; } template size_t EstimateMemoryUsage(const std::unordered_set& set) { using value_type = typename std::unordered_set::value_type; return EstimateHashMapMemoryUsage(set.bucket_count(), set.size()) + EstimateIterableMemoryUsage(set); } template size_t EstimateMemoryUsage(const std::unordered_multiset& set) { using value_type = typename std::unordered_multiset::value_type; return EstimateHashMapMemoryUsage(set.bucket_count(), set.size()) + EstimateIterableMemoryUsage(set); } template size_t EstimateMemoryUsage(const std::unordered_map& map) { using value_type = typename std::unordered_map::value_type; return EstimateHashMapMemoryUsage(map.bucket_count(), map.size()) + EstimateIterableMemoryUsage(map); } template size_t EstimateMemoryUsage(const std::unordered_multimap& map) { using value_type = typename std::unordered_multimap::value_type; return EstimateHashMapMemoryUsage(map.bucket_count(), map.size()) + EstimateIterableMemoryUsage(map); } // std::deque template size_t EstimateMemoryUsage(const std::deque& deque) { // Since std::deque implementations are wildly different // (see crbug.com/674287), we can't have one "good enough" // way to estimate. // kBlockSize - minimum size of a block, in bytes // kMinBlockLength - number of elements in a block // if sizeof(T) > kBlockSize #if defined(_LIBCPP_VERSION) size_t kBlockSize = 4096; size_t kMinBlockLength = 16; #elif defined(__GLIBCXX__) size_t kBlockSize = 512; size_t kMinBlockLength = 1; #elif defined(_MSC_VER) size_t kBlockSize = 16; size_t kMinBlockLength = 1; #else size_t kBlockSize = 0; size_t kMinBlockLength = 1; #endif size_t block_length = (sizeof(T) > kBlockSize) ? kMinBlockLength : kBlockSize / sizeof(T); size_t blocks = (deque.size() + block_length - 1) / block_length; #if defined(__GLIBCXX__) // libstdc++: deque always has at least one block if (!blocks) blocks = 1; #endif #if defined(_LIBCPP_VERSION) // libc++: deque keeps at most two blocks when it shrinks, // so even if the size is zero, deque might be holding up // to 4096 * 2 bytes. One way to know whether deque has // ever allocated (and hence has 1 or 2 blocks) is to check // iterator's pointer. Non-zero value means that deque has // at least one block. if (!blocks && deque.begin().operator->()) blocks = 1; #endif return (blocks * block_length * sizeof(T)) + EstimateIterableMemoryUsage(deque); } // Container adapters template size_t EstimateMemoryUsage(const std::queue& queue) { return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue)); } template size_t EstimateMemoryUsage(const std::priority_queue& queue) { return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue)); } template size_t EstimateMemoryUsage(const std::stack& stack) { return EstimateMemoryUsage(internal::GetUnderlyingContainer(stack)); } // base::circular_deque template size_t EstimateMemoryUsage(const base::circular_deque& deque) { return sizeof(T) * deque.capacity() + EstimateIterableMemoryUsage(deque); } // Flat containers template size_t EstimateMemoryUsage(const base::flat_set& set) { using value_type = typename base::flat_set::value_type; return sizeof(value_type) * set.capacity() + EstimateIterableMemoryUsage(set); } template size_t EstimateMemoryUsage(const base::flat_map& map) { using value_type = typename base::flat_map::value_type; return sizeof(value_type) * map.capacity() + EstimateIterableMemoryUsage(map); } } // namespace trace_event } // namespace base #endif // BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_