mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2024-12-01 01:36:09 +03:00
583 lines
18 KiB
C++
583 lines
18 KiB
C++
// 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 <stdint.h>
|
|
|
|
#include <array>
|
|
#include <deque>
|
|
#include <list>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <queue>
|
|
#include <set>
|
|
#include <stack>
|
|
#include <string>
|
|
#include <type_traits>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#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<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);
|
|
|
|
// std::shared_ptr
|
|
|
|
template <class T>
|
|
size_t EstimateMemoryUsage(const std::shared_ptr<T>& ptr);
|
|
|
|
// 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>
|
|
size_t EstimateMemoryUsage(const base::LinkedList<T>& 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);
|
|
|
|
template <class T, class A>
|
|
size_t EstimateMemoryUsage(const std::deque<T, A>& deque);
|
|
|
|
template <class T, class C>
|
|
size_t EstimateMemoryUsage(const std::queue<T, C>& queue);
|
|
|
|
template <class T, class C>
|
|
size_t EstimateMemoryUsage(const std::priority_queue<T, C>& queue);
|
|
|
|
template <class T, class C>
|
|
size_t EstimateMemoryUsage(const std::stack<T, C>& stack);
|
|
|
|
template <class T>
|
|
size_t EstimateMemoryUsage(const base::circular_deque<T>& deque);
|
|
|
|
template <class T, class C>
|
|
size_t EstimateMemoryUsage(const base::flat_set<T, C>& set);
|
|
|
|
template <class K, class V, class C>
|
|
size_t EstimateMemoryUsage(const base::flat_map<K, V, C>& map);
|
|
|
|
// TODO(dskiba):
|
|
// std::forward_list
|
|
|
|
// 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>
|
|
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,
|
|
"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 &&
|
|
std::is_trivially_destructible<T>::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 <class A>
|
|
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<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 uint8_t* cstr = reinterpret_cast<const uint8_t*>(string.c_str());
|
|
const uint8_t* inline_cstr = reinterpret_cast<const uint8_t*>(&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 <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::shared_ptr
|
|
|
|
template <class T>
|
|
size_t EstimateMemoryUsage(const std::shared_ptr<T>& 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 <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);
|
|
}
|
|
|
|
template <class T>
|
|
size_t EstimateMemoryUsage(const base::LinkedList<T>& list) {
|
|
size_t memory_usage = 0u;
|
|
for (base::LinkNode<T>* 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 <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);
|
|
}
|
|
|
|
// std::deque
|
|
|
|
template <class T, class A>
|
|
size_t EstimateMemoryUsage(const std::deque<T, A>& 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 <class T, class C>
|
|
size_t EstimateMemoryUsage(const std::queue<T, C>& queue) {
|
|
return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue));
|
|
}
|
|
|
|
template <class T, class C>
|
|
size_t EstimateMemoryUsage(const std::priority_queue<T, C>& queue) {
|
|
return EstimateMemoryUsage(internal::GetUnderlyingContainer(queue));
|
|
}
|
|
|
|
template <class T, class C>
|
|
size_t EstimateMemoryUsage(const std::stack<T, C>& stack) {
|
|
return EstimateMemoryUsage(internal::GetUnderlyingContainer(stack));
|
|
}
|
|
|
|
// base::circular_deque
|
|
|
|
template <class T>
|
|
size_t EstimateMemoryUsage(const base::circular_deque<T>& deque) {
|
|
return sizeof(T) * deque.capacity() + EstimateIterableMemoryUsage(deque);
|
|
}
|
|
|
|
// Flat containers
|
|
|
|
template <class T, class C>
|
|
size_t EstimateMemoryUsage(const base::flat_set<T, C>& set) {
|
|
using value_type = typename base::flat_set<T, C>::value_type;
|
|
return sizeof(value_type) * set.capacity() + EstimateIterableMemoryUsage(set);
|
|
}
|
|
|
|
template <class K, class V, class C>
|
|
size_t EstimateMemoryUsage(const base::flat_map<K, V, C>& map) {
|
|
using value_type = typename base::flat_map<K, V, C>::value_type;
|
|
return sizeof(value_type) * map.capacity() + EstimateIterableMemoryUsage(map);
|
|
}
|
|
|
|
} // namespace trace_event
|
|
} // namespace base
|
|
|
|
#endif // BASE_TRACE_EVENT_MEMORY_USAGE_ESTIMATOR_H_
|