]> git.proxmox.com Git - ceph.git/blame - ceph/src/include/mempool.h
update sources to v12.1.0
[ceph.git] / ceph / src / include / mempool.h
CommitLineData
7c673cae
FG
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3/*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2016 Allen Samuels <allen.samuels@sandisk.com>
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 *
13 */
14
15#ifndef _CEPH_INCLUDE_MEMPOOL_H
16#define _CEPH_INCLUDE_MEMPOOL_H
17
18#include <cstddef>
19#include <map>
20#include <unordered_map>
21#include <set>
22#include <vector>
23#include <list>
24#include <mutex>
25#include <atomic>
26#include <typeinfo>
27
28#include <common/Formatter.h>
29#include "include/assert.h"
30
31
32/*
33
34Memory Pools
35============
36
37A memory pool is a method for accounting the consumption of memory of
38a set of containers.
39
40Memory pools are statically declared (see pool_index_t).
41
42Each memory pool tracks the number of bytes and items it contains.
43
44Allocators can be declared and associated with a type so that they are
45tracked independently of the pool total. This additional accounting
46is optional and only incurs an overhead if the debugging is enabled at
47runtime. This allows developers to see what types are consuming the
48pool resources.
49
50
51Declaring
52---------
53
54Using memory pools is very easy.
55
56To create a new memory pool, simply add a new name into the list of
57memory pools that's defined in "DEFINE_MEMORY_POOLS_HELPER". That's
58it. :)
59
60For each memory pool that's created a C++ namespace is also
61automatically created (name is same as in DEFINE_MEMORY_POOLS_HELPER).
62That namespace contains a set of common STL containers that are predefined
63with the appropriate allocators.
64
31f18b77 65Thus for mempool "osd" we have automatically available to us:
7c673cae 66
31f18b77
FG
67 mempool::osd::map
68 mempool::osd::multimap
69 mempool::osd::set
70 mempool::osd::multiset
71 mempool::osd::list
72 mempool::osd::vector
73 mempool::osd::unordered_map
7c673cae
FG
74
75
76Putting objects in a mempool
77----------------------------
78
79In order to use a memory pool with a particular type, a few additional
80declarations are needed.
81
82For a class:
83
84 struct Foo {
85 MEMPOOL_CLASS_HELPERS();
86 ...
87 };
88
89Then, in an appropriate .cc file,
90
31f18b77 91 MEMPOOL_DEFINE_OBJECT_FACTORY(Foo, foo, osd);
7c673cae
FG
92
93The second argument can generally be identical to the first, except
94when the type contains a nested scope. For example, for
95BlueStore::Onode, we need to do
96
97 MEMPOOL_DEFINE_OBJECT_FACTORY(BlueStore::Onode, bluestore_onode,
98 bluestore_meta);
99
100(This is just because we need to name some static variables and we
101can't use :: in a variable name.)
102
103In order to use the STL containers, simply use the namespaced variant
104of the container type. For example,
105
31f18b77 106 mempool::osd::map<int> myvec;
7c673cae
FG
107
108Introspection
109-------------
110
111The simplest way to interrogate the process is with
112
113 Formater *f = ...
114 mempool::dump(f);
115
116This will dump information about *all* memory pools. When debug mode
117is enabled, the runtime complexity of dump is O(num_shards *
118num_types). When debug name is disabled it is O(num_shards).
119
120You can also interrogate a specific pool programmatically with
121
122 size_t bytes = mempool::unittest_2::allocated_bytes();
123 size_t items = mempool::unittest_2::allocated_items();
124
125The runtime complexity is O(num_shards).
126
127Note that you cannot easily query per-type, primarily because debug
128mode is optional and you should not rely on that information being
129available.
130
131*/
132
133namespace mempool {
134
135// --------------------------------------------------------------
136// define memory pools
137
138#define DEFINE_MEMORY_POOLS_HELPER(f) \
139 f(bloom_filter) \
7c673cae 140 f(bluestore_alloc) \
31f18b77
FG
141 f(bluestore_cache_data) \
142 f(bluestore_cache_onode) \
143 f(bluestore_cache_other) \
7c673cae 144 f(bluestore_fsck) \
31f18b77
FG
145 f(bluestore_txc) \
146 f(bluestore_writing_deferred) \
147 f(bluestore_writing) \
7c673cae 148 f(bluefs) \
31f18b77 149 f(buffer_anon) \
7c673cae 150 f(buffer_meta) \
7c673cae 151 f(osd) \
31f18b77
FG
152 f(osd_mapbl) \
153 f(osd_pglog) \
7c673cae
FG
154 f(osdmap) \
155 f(osdmap_mapping) \
31f18b77 156 f(pgmap) \
7c673cae
FG
157 f(unittest_1) \
158 f(unittest_2)
159
160
161// give them integer ids
162#define P(x) mempool_##x,
163enum pool_index_t {
164 DEFINE_MEMORY_POOLS_HELPER(P)
165 num_pools // Must be last.
166};
167#undef P
168
169extern bool debug_mode;
170extern void set_debug_mode(bool d);
171
172// --------------------------------------------------------------
173class pool_t;
174
175// we shard pool stats across many shard_t's to reduce the amount
176// of cacheline ping pong.
177enum {
178 num_shard_bits = 5
179};
180enum {
181 num_shards = 1 << num_shard_bits
182};
183
184// align shard to a cacheline
185struct shard_t {
186 std::atomic<size_t> bytes = {0};
187 std::atomic<size_t> items = {0};
188 char __padding[128 - sizeof(std::atomic<size_t>)*2];
189} __attribute__ ((aligned (128)));
190
191static_assert(sizeof(shard_t) == 128, "shard_t should be cacheline-sized");
192
193struct stats_t {
194 ssize_t items = 0;
195 ssize_t bytes = 0;
196 void dump(ceph::Formatter *f) const {
197 f->dump_int("items", items);
198 f->dump_int("bytes", bytes);
199 }
31f18b77
FG
200
201 stats_t& operator+=(const stats_t& o) {
202 items += o.items;
203 bytes += o.bytes;
204 return *this;
205 }
7c673cae
FG
206};
207
208pool_t& get_pool(pool_index_t ix);
209const char *get_pool_name(pool_index_t ix);
210
211struct type_t {
212 const char *type_name;
213 size_t item_size;
214 std::atomic<ssize_t> items = {0}; // signed
215};
216
217struct type_info_hash {
218 std::size_t operator()(const std::type_info& k) const {
219 return k.hash_code();
220 }
221};
222
223class pool_t {
224 shard_t shard[num_shards];
225
226 mutable std::mutex lock; // only used for types list
227 std::unordered_map<const char *, type_t> type_map;
228
229public:
230 //
231 // How much this pool consumes. O(<num_shards>)
232 //
233 size_t allocated_bytes() const;
234 size_t allocated_items() const;
235
31f18b77
FG
236 void adjust_count(ssize_t items, ssize_t bytes);
237
7c673cae
FG
238 shard_t* pick_a_shard() {
239 // Dirt cheap, see:
240 // http://fossies.org/dox/glibc-2.24/pthread__self_8c_source.html
241 size_t me = (size_t)pthread_self();
242 size_t i = (me >> 3) & ((1 << num_shard_bits) - 1);
243 return &shard[i];
244 }
245
246 type_t *get_type(const std::type_info& ti, size_t size) {
247 std::lock_guard<std::mutex> l(lock);
248 auto p = type_map.find(ti.name());
249 if (p != type_map.end()) {
250 return &p->second;
251 }
252 type_t &t = type_map[ti.name()];
253 t.type_name = ti.name();
254 t.item_size = size;
255 return &t;
256 }
257
258 // get pool stats. by_type is not populated if !debug
259 void get_stats(stats_t *total,
260 std::map<std::string, stats_t> *by_type) const;
261
31f18b77 262 void dump(ceph::Formatter *f, stats_t *ptotal=0) const;
7c673cae
FG
263};
264
265void dump(ceph::Formatter *f);
266
267
268// STL allocator for use with containers. All actual state
269// is stored in the static pool_allocator_base_t, which saves us from
270// passing the allocator to container constructors.
271
272template<pool_index_t pool_ix, typename T>
273class pool_allocator {
274 pool_t *pool;
275 type_t *type = nullptr;
276
277public:
278 typedef pool_allocator<pool_ix, T> allocator_type;
279 typedef T value_type;
280 typedef value_type *pointer;
281 typedef const value_type * const_pointer;
282 typedef value_type& reference;
283 typedef const value_type& const_reference;
284 typedef std::size_t size_type;
285 typedef std::ptrdiff_t difference_type;
286
287 template<typename U> struct rebind {
288 typedef pool_allocator<pool_ix,U> other;
289 };
290
291 void init(bool force_register) {
292 pool = &get_pool(pool_ix);
293 if (debug_mode || force_register) {
294 type = pool->get_type(typeid(T), sizeof(T));
295 }
296 }
297
298 pool_allocator(bool force_register=false) {
299 init(force_register);
300 }
301 template<typename U>
302 pool_allocator(const pool_allocator<pool_ix,U>&) {
303 init(false);
304 }
305
306 T* allocate(size_t n, void *p = nullptr) {
307 size_t total = sizeof(T) * n;
308 shard_t *shard = pool->pick_a_shard();
309 shard->bytes += total;
310 shard->items += n;
311 if (type) {
312 type->items += n;
313 }
314 T* r = reinterpret_cast<T*>(new char[total]);
315 return r;
316 }
317
318 void deallocate(T* p, size_t n) {
319 size_t total = sizeof(T) * n;
320 shard_t *shard = pool->pick_a_shard();
321 shard->bytes -= total;
322 shard->items -= n;
323 if (type) {
324 type->items -= n;
325 }
326 delete[] reinterpret_cast<char*>(p);
327 }
328
329 T* allocate_aligned(size_t n, size_t align, void *p = nullptr) {
330 size_t total = sizeof(T) * n;
331 shard_t *shard = pool->pick_a_shard();
332 shard->bytes += total;
333 shard->items += n;
334 if (type) {
335 type->items += n;
336 }
337 char *ptr;
338 int rc = ::posix_memalign((void**)(void*)&ptr, align, total);
339 if (rc)
340 throw std::bad_alloc();
341 T* r = reinterpret_cast<T*>(ptr);
342 return r;
343 }
344
345 void deallocate_aligned(T* p, size_t n) {
346 size_t total = sizeof(T) * n;
347 shard_t *shard = pool->pick_a_shard();
348 shard->bytes -= total;
349 shard->items -= n;
350 if (type) {
351 type->items -= n;
352 }
353 ::free(p);
354 }
355
356 void destroy(T* p) {
357 p->~T();
358 }
359
360 template<class U>
361 void destroy(U *p) {
362 p->~U();
363 }
364
365 void construct(T* p, const T& val) {
366 ::new ((void *)p) T(val);
367 }
368
369 template<class U, class... Args> void construct(U* p,Args&&... args) {
370 ::new((void *)p) U(std::forward<Args>(args)...);
371 }
372
373 bool operator==(const pool_allocator&) const { return true; }
374 bool operator!=(const pool_allocator&) const { return false; }
375};
376
377
378// Namespace mempool
379
380#define P(x) \
381 namespace x { \
382 static const mempool::pool_index_t id = mempool::mempool_##x; \
383 template<typename v> \
384 using pool_allocator = mempool::pool_allocator<id,v>; \
385 \
386 using string = std::basic_string<char,std::char_traits<char>, \
387 pool_allocator<char>>; \
388 \
389 template<typename k,typename v, typename cmp = std::less<k> > \
390 using map = std::map<k, v, cmp, \
391 pool_allocator<std::pair<const k,v>>>; \
392 \
393 template<typename k,typename v, typename cmp = std::less<k> > \
394 using multimap = std::multimap<k,v,cmp, \
395 pool_allocator<std::pair<const k, \
396 v>>>; \
397 \
398 template<typename k, typename cmp = std::less<k> > \
399 using set = std::set<k,cmp,pool_allocator<k>>; \
400 \
401 template<typename v> \
402 using list = std::list<v,pool_allocator<v>>; \
403 \
404 template<typename v> \
405 using vector = std::vector<v,pool_allocator<v>>; \
406 \
407 template<typename k, typename v, \
408 typename h=std::hash<k>, \
409 typename eq = std::equal_to<k>> \
410 using unordered_map = \
411 std::unordered_map<k,v,h,eq,pool_allocator<std::pair<const k,v>>>;\
412 \
413 inline size_t allocated_bytes() { \
414 return mempool::get_pool(id).allocated_bytes(); \
415 } \
416 inline size_t allocated_items() { \
417 return mempool::get_pool(id).allocated_items(); \
418 } \
419 };
420
421DEFINE_MEMORY_POOLS_HELPER(P)
422
423#undef P
424
425};
426
427
428
429// Use this for any type that is contained by a container (unless it
430// is a class you defined; see below).
431#define MEMPOOL_DECLARE_FACTORY(obj, factoryname, pool) \
432 namespace mempool { \
433 namespace pool { \
434 extern pool_allocator<obj> alloc_##factoryname; \
435 } \
436 }
437
438#define MEMPOOL_DEFINE_FACTORY(obj, factoryname, pool) \
439 namespace mempool { \
440 namespace pool { \
441 pool_allocator<obj> alloc_##factoryname = {true}; \
442 } \
443 }
444
445// Use this for each class that belongs to a mempool. For example,
446//
447// class T {
448// MEMPOOL_CLASS_HELPERS();
449// ...
450// };
451//
452#define MEMPOOL_CLASS_HELPERS() \
453 void *operator new(size_t size); \
454 void *operator new[](size_t size) noexcept { \
455 assert(0 == "no array new"); \
456 return nullptr; } \
457 void operator delete(void *); \
458 void operator delete[](void *) { assert(0 == "no array delete"); }
459
460
461// Use this in some particular .cc file to match each class with a
462// MEMPOOL_CLASS_HELPERS().
463#define MEMPOOL_DEFINE_OBJECT_FACTORY(obj,factoryname,pool) \
464 MEMPOOL_DEFINE_FACTORY(obj, factoryname, pool) \
465 void *obj::operator new(size_t size) { \
466 return mempool::pool::alloc_##factoryname.allocate(1); \
467 } \
468 void obj::operator delete(void *p) { \
469 return mempool::pool::alloc_##factoryname.deallocate((obj*)p, 1); \
470 }
471
472#endif