Index: base/trace_event/memory_usage_estimator.h |
diff --git a/base/trace_event/memory_usage_estimator.h b/base/trace_event/memory_usage_estimator.h |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3fd8b22d1f1bbf3a57cdba6a26e1c99a960da1f1 |
--- /dev/null |
+++ b/base/trace_event/memory_usage_estimator.h |
@@ -0,0 +1,410 @@ |
+// 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 <array> |
+#include <list> |
+#include <map> |
+#include <memory> |
+#include <set> |
+#include <string> |
+#include <type_traits> |
+#include <unordered_map> |
+#include <unordered_set> |
+#include <vector> |
+ |
+#include "base/template_util.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<int, std::vector<T>>). |
+// |
+// 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 <class T> |
+auto EstimateMemoryUsage(const T& object) |
+ -> decltype(object.EstimateMemoryUsage()); |
+ |
+// String |
+ |
+template <class C, class T, class A> |
+size_t EstimateMemoryUsage(const std::basic_string<C, T, A>& string); |
+ |
+// Arrays |
+ |
+template <class T, size_t N> |
+size_t EstimateMemoryUsage(const std::array<T, N>& array); |
+ |
+template <class T, size_t N> |
+size_t EstimateMemoryUsage(T (&array)[N]); |
+ |
+template <class T> |
+size_t EstimateMemoryUsage(const T* array, size_t array_length); |
+ |
+// std::unique_ptr |
+ |
+template <class T, class D> |
+size_t EstimateMemoryUsage(const std::unique_ptr<T, D>& ptr); |
+ |
+template <class T, class D> |
+size_t EstimateMemoryUsage(const std::unique_ptr<T[], D>& array, |
+ size_t array_length); |
+ |
+// Containers |
+ |
+template <class F, class S> |
+size_t EstimateMemoryUsage(const std::pair<F, S>& pair); |
+ |
+template <class T, class A> |
+size_t EstimateMemoryUsage(const std::vector<T, A>& vector); |
+ |
+template <class T, class A> |
+size_t EstimateMemoryUsage(const std::list<T, A>& list); |
+ |
+template <class T, class C, class A> |
+size_t EstimateMemoryUsage(const std::set<T, C, A>& set); |
+ |
+template <class T, class C, class A> |
+size_t EstimateMemoryUsage(const std::multiset<T, C, A>& set); |
+ |
+template <class K, class V, class C, class A> |
+size_t EstimateMemoryUsage(const std::map<K, V, C, A>& map); |
+ |
+template <class K, class V, class C, class A> |
+size_t EstimateMemoryUsage(const std::multimap<K, V, C, A>& map); |
+ |
+template <class T, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_set<T, H, KE, A>& set); |
+ |
+template <class T, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_multiset<T, H, KE, A>& set); |
+ |
+template <class K, class V, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_map<K, V, H, KE, A>& map); |
+ |
+template <class K, class V, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_multimap<K, V, H, KE, A>& map); |
+ |
+// TODO(dskiba): |
+// std::forward_list |
+// std::deque |
+// std::queue |
+// std::stack |
+// std::queue |
+// std::priority_queue |
+ |
+// Definitions |
+ |
+namespace internal { |
+ |
+// HasEMU<T>::value is true iff EstimateMemoryUsage(T) is available. |
+// (This is the default version, which is false.) |
+template <class T, class X = void> |
+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 <class T> |
+struct HasEMU< |
+ T, |
+ typename std::enable_if<std::is_same< |
+ size_t, |
+ decltype(EstimateMemoryUsage(std::declval<const T&>()))>::value>::type> |
+ : std::true_type {}; |
+ |
+// EMUCaller<T> 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<int, MyClass>). |
+// 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 <class T, class X = void> |
dcheng
2016/11/10 06:45:46
Nit: I think class X = void may no longer be neces
|
+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<T, std::false_type>::value, |
dcheng
2016/11/10 06:45:46
It's technically possible for std::false to be sup
|
+ "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 <class T> |
+struct EMUCaller<T, typename std::enable_if<HasEMU<T>::value>::type> { |
+ static size_t Call(const T& value) { return EstimateMemoryUsage(value); } |
+}; |
+ |
+template <class T> |
+struct EMUCaller< |
+ T, |
+ typename std::enable_if<!HasEMU<T>::value && |
+ is_trivially_destructible<T>::value>::type> { |
+ static size_t Call(const T& value) { return 0; } |
+}; |
+ |
+} // namespace internal |
+ |
+// Proxy that deducts T and calls EMUCaller<T>. |
+// To be used by EstimateMemoryUsage() implementations for containers. |
+template <class T> |
+size_t EstimateItemMemoryUsage(const T& value) { |
+ return internal::EMUCaller<T>::Call(value); |
+} |
+ |
+template <class I> |
+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 <class T> |
+auto EstimateMemoryUsage(const T& object) |
+ -> decltype(object.EstimateMemoryUsage()) { |
+ static_assert( |
+ std::is_same<decltype(object.EstimateMemoryUsage()), size_t>::value, |
+ "'T::EstimateMemoryUsage() const' must return size_t."); |
+ return object.EstimateMemoryUsage(); |
+} |
+ |
+// String |
+ |
+template <class C, class T, class A> |
+size_t EstimateMemoryUsage(const std::basic_string<C, T, A>& string) { |
+ using string_type = std::basic_string<C, T, A>; |
+ 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 char* cstr = reinterpret_cast<const char*>(string.c_str()); |
dcheng
2016/11/10 06:45:46
Perhaps use uint8_t to make it clear that we expli
|
+ const char* inline_cstr = reinterpret_cast<const char*>(&string); |
+ if (cstr >= inline_cstr && cstr < inline_cstr + sizeof(string)) { |
+ // SSO string |
+ return 0; |
+ } |
+ return (string.capacity() + 1) * sizeof(value_type); |
+} |
+ |
+// Arrays |
+ |
+template <class T, size_t N> |
+size_t EstimateMemoryUsage(const std::array<T, N>& array) { |
+ return EstimateIterableMemoryUsage(array); |
+} |
+ |
+template <class T, size_t N> |
+size_t EstimateMemoryUsage(T (&array)[N]) { |
+ return EstimateIterableMemoryUsage(array); |
+} |
+ |
+template <class T> |
+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 <class T, class D> |
+size_t EstimateMemoryUsage(const std::unique_ptr<T, D>& ptr) { |
+ return ptr ? (sizeof(T) + EstimateItemMemoryUsage(*ptr)) : 0; |
+} |
+ |
+template <class T, class D> |
+size_t EstimateMemoryUsage(const std::unique_ptr<T[], D>& array, |
+ size_t array_length) { |
+ return EstimateMemoryUsage(array.get(), array_length); |
+} |
+ |
+// std::pair |
+ |
+template <class F, class S> |
+size_t EstimateMemoryUsage(const std::pair<F, S>& pair) { |
+ return EstimateItemMemoryUsage(pair.first) + |
+ EstimateItemMemoryUsage(pair.second); |
+} |
+ |
+// std::vector |
+ |
+template <class T, class A> |
+size_t EstimateMemoryUsage(const std::vector<T, A>& vector) { |
+ return sizeof(T) * vector.capacity() + EstimateIterableMemoryUsage(vector); |
+} |
+ |
+// std::list |
+ |
+template <class T, class A> |
+size_t EstimateMemoryUsage(const std::list<T, A>& list) { |
+ using value_type = typename std::list<T, A>::value_type; |
+ struct Node { |
+ Node* prev; |
+ Node* next; |
+ value_type value; |
+ }; |
+ return sizeof(Node) * list.size() + |
+ EstimateIterableMemoryUsage(list); |
+} |
+ |
+// Tree containers |
+ |
+template <class V> |
+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 <class T, class C, class A> |
+size_t EstimateMemoryUsage(const std::set<T, C, A>& set) { |
+ using value_type = typename std::set<T, C, A>::value_type; |
+ return EstimateTreeMemoryUsage<value_type>(set.size()) + |
+ EstimateIterableMemoryUsage(set); |
+} |
+ |
+template <class T, class C, class A> |
+size_t EstimateMemoryUsage(const std::multiset<T, C, A>& set) { |
+ using value_type = typename std::multiset<T, C, A>::value_type; |
+ return EstimateTreeMemoryUsage<value_type>(set.size()) + |
+ EstimateIterableMemoryUsage(set); |
+} |
+ |
+template <class K, class V, class C, class A> |
+size_t EstimateMemoryUsage(const std::map<K, V, C, A>& map) { |
+ using value_type = typename std::map<K, V, C, A>::value_type; |
+ return EstimateTreeMemoryUsage<value_type>(map.size()) + |
+ EstimateIterableMemoryUsage(map); |
+} |
+ |
+template <class K, class V, class C, class A> |
+size_t EstimateMemoryUsage(const std::multimap<K, V, C, A>& map) { |
+ using value_type = typename std::multimap<K, V, C, A>::value_type; |
+ return EstimateTreeMemoryUsage<value_type>(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 <class V> |
+size_t HashMapBucketCountForTesting(size_t bucket_count) { |
+ return bucket_count; |
+} |
+ |
+} // namespace internal |
+ |
+template <class V> |
+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<V>(bucket_count); |
+ return sizeof(Bucket) * bucket_count + sizeof(Node) * size; |
+} |
+ |
+template <class K, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_set<K, H, KE, A>& set) { |
+ using value_type = typename std::unordered_set<K, H, KE, A>::value_type; |
+ return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(), |
+ set.size()) + |
+ EstimateIterableMemoryUsage(set); |
+} |
+ |
+template <class K, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_multiset<K, H, KE, A>& set) { |
+ using value_type = typename std::unordered_multiset<K, H, KE, A>::value_type; |
+ return EstimateHashMapMemoryUsage<value_type>(set.bucket_count(), |
+ set.size()) + |
+ EstimateIterableMemoryUsage(set); |
+} |
+ |
+template <class K, class V, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_map<K, V, H, KE, A>& map) { |
+ using value_type = typename std::unordered_map<K, V, H, KE, A>::value_type; |
+ return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(), |
+ map.size()) + |
+ EstimateIterableMemoryUsage(map); |
+} |
+ |
+template <class K, class V, class H, class KE, class A> |
+size_t EstimateMemoryUsage(const std::unordered_multimap<K, V, H, KE, A>& map) { |
+ using value_type = |
+ typename std::unordered_multimap<K, V, H, KE, A>::value_type; |
+ return EstimateHashMapMemoryUsage<value_type>(map.bucket_count(), |
+ map.size()) + |
+ EstimateIterableMemoryUsage(map); |
+} |
+ |
+} // namespace trace_event |
+} // namespace base |
+ |
+#endif // BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_ |