MySQL 9.1.0
Source Code Documentation
block.h
Go to the documentation of this file.
1/* Copyright (c) 2019, 2024, Oracle and/or its affiliates.
2
3This program is free software; you can redistribute it and/or modify it under
4the terms of the GNU General Public License, version 2.0, as published by the
5Free Software Foundation.
6
7This program is designed to work with certain software (including
8but not limited to OpenSSL) that is licensed under separate terms,
9as designated in a particular file or component or in included license
10documentation. The authors of MySQL hereby grant you an additional
11permission to link the program and your derivative works with the
12separately licensed software that they have either included with
13the program or referenced in the documentation.
14
15This program is distributed in the hope that it will be useful, but WITHOUT
16ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
18for more details.
19
20You should have received a copy of the GNU General Public License along with
21this program; if not, write to the Free Software Foundation, Inc.,
2251 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
23
24/** @file storage/temptable/include/temptable/block.h
25Block abstraction for temptable-allocator. */
26
27#ifndef TEMPTABLE_BLOCK_H
28#define TEMPTABLE_BLOCK_H
29
30#include <cstddef> // size_t
31#include <cstdint> // uint8_t, uintptr_t
32#include <limits> // std::numeric_limits
33#include <sstream> // std::stringstream
34#include <string> // std::string
35
36#include "memory_debugging.h"
37#include "my_dbug.h"
42
43namespace temptable {
44
45/** Initialize the PSI memory engine. */
46void Block_PSI_init();
47/** Log logical (Chunk) memory allocation.
48 *
49 * [in] Number of bytes allocated
50 * */
52/** Log logical (Chunk) memory deallocation.
53 *
54 * [in] Number of bytes deallocated
55 * */
57/** Log physical memory allocation of a Block located in RAM.
58 *
59 * [in] Pointer to user memory block
60 * [in] Number of bytes allocated
61 * */
62void Block_PSI_track_physical_ram_allocation(void *ptr, size_t size);
63/** Log physical memory deallocation of a Block located in RAM.
64 *
65 * [in] Pointer to PSI header
66 * */
68/** Log physical memory allocation of a Block located in MMAP-ed file.
69 *
70 * [in] Pointer to user memory block
71 * [in] Number of bytes allocated
72 * */
74/** Log physical memory deallocation of a Block located in MMAP-ed file.
75 *
76 * [in] Pointer to PSI header
77 * */
79
80/** Memory-block abstraction whose purpose is to serve as a building block
81 * for custom memory-allocator implementations.
82 *
83 * TL;DR How it works:
84 * Instantiation:
85 * - With given size and given memory source, Block will allocate memory
86 * and adjust its Header metadata with the relevant information.
87 * Allocation:
88 * - From allocated memory space, Block finds out what is the next
89 * available slot to fit the new Chunk into.
90 * - Creates a new Chunk with the address pointing to that slot.
91 * - Increments the number of allocated chunks.
92 * - Returns a Chunk.
93 * Deallocation:
94 * - Decrements the number of allocated chunks.
95 * - Returns current number of allocated chunks.
96 * Destruction:
97 * - Simply deallocates the memory.
98 *
99 * Now, more detailed description ...
100 *
101 * Normally, custom memory-allocators will feed clients' memory allocation
102 * and deallocation requests solely through the provided Block interface which
103 * enables allocators not to worry about the whole lot of low-level memory
104 * byte-juggling but to focus on application-level details.
105 *
106 * Block, once created, will occupy at-least (see below why) the specified
107 * amount of memory after which it will be able to serve client-requested
108 * allocations and deallocations in logical units called Chunks. Chunk is
109 * an arbitrarily-sized view over a region of memory allocated during the
110 * Block creation. Block can fit as many Chunks as there is free memory space
111 * left in it. Once there is no free space left, another Block of memory has
112 * to be created. Block is not resizeable. E.g. 4KB-sized Block can feed 1x4KB,
113 * 2x2KB, 1KB+3KB or any other combination of Chunks whose total size does not
114 * exceed the Block size (4KB).
115 *
116 * Organizing Block memory into Chunks is implementation house-keeping detail
117 * stored in its Header metadata region. Block does not maintain the list of
118 * Chunks, it only ever keeps the number of currently allocated Chunks and
119 * the offset to the first memory location available to feed the next
120 * allocation request.
121 *
122 * While still using the same interface, custom memory-allocators are able to
123 * choose where should the Block allocate actual memory from. It could be
124 * anything defined by Source but currently only RAM and MMAP-ed files are
125 * available and implemented as options.
126 *
127 * For the benefit of (amortized) constant-time allocations, Block does not
128 * re-use or do any other special operations over deallocated Chunks so
129 * memory-allocators which will be using it may suffer from block-level
130 * memory-fragmentation and consequently higher memory-consumption. Exceptions
131 * are deallocations of first and last Chunks in a Block when it is possible
132 * to easily re-adjust the offset and therefore be able to re-use that part
133 * of memory.
134 *
135 * Another big advantage, which is very closely related to constant-time
136 * allocations, is that it minimizes the number of system-calls required to
137 * allocate and deallocate the memory which consequently may lower the
138 * process-level memory-fragmentation.
139 *
140 * Block size does not necessarily end-up being the size originally requested by
141 * the client but it will be actually implicitly rounded to the next multiple
142 * of CPU word-size which may result in better memory utilization. Actual block
143 * size can be queried through the Block interface.
144 *
145 * To optimize for the CPU memory-access, but also to enable code not to
146 * segfault on architectures which do not support unaligned-memory-access
147 * (e.g. SPARC), Block will always adjust requested Chunk allocation size to
148 * match the size which is rounded to the next multiple of CPU word-size
149 * (Block::ALIGN_TO constant). End result is that Block might end up allocating
150 * just a few more bytes bigger Chunk than actually requested but that
151 * information, however, does not need to be maintained or cared about by the
152 * client code.
153 *
154 * Along with the small space overhead due to the automatic word-size-adjustment
155 * of Chunk size, each Block allocation will also have a few bytes overhead for
156 * maintaining the Header metadata (Header::SIZE) as well as for maintaining the
157 * Chunk metadata (Chunk::METADATA_SIZE). Implementation and data layout details
158 * can be found at respective header file declarations.
159 *
160 * All dirty-implementation details are hidden in Header implementation
161 * which makes sure that proper care is taken to handle chunk offsets,
162 * available slots, number of present chunks etc. */
163class Block : private Header {
164 public:
165 /** Block will self-adjust all requested allocation-sizes to the multiple of
166 * this value. */
167 static constexpr size_t ALIGN_TO = alignof(void *);
168
169 public:
170 /** Default constructor which creates an empty Block. */
171 Block() noexcept = default;
172
173 /** Constructor which creates a Block of given size from the given
174 * memory source.
175 *
176 * [in] Block size in bytes.
177 * [in] Source where Block will allocate actual memory from. */
178 Block(size_t size, Source memory_source);
179
180 /** Constructor which creates a Block from given Chunk. Chunk holds
181 * just enough information so we can deduce which Block does it belong to.
182 *
183 * [in] Existing Chunk in memory. */
184 explicit Block(Chunk chunk) noexcept;
185
186 /** Equality operator.
187 *
188 * [in] Block to compare it against.
189 * @return true if two Blocks are equal (pointing to the same memory
190 * location). */
191 bool operator==(const Block &other) const;
192
193 /** Inequality operator.
194 *
195 * [in] Block to compare it against.
196 * @return true if two Blocks are not equal (not pointing to the same
197 * memory location). */
198 bool operator!=(const Block &other) const;
199
200 /** Allocate a Chunk from a Block.
201 *
202 * [in] Size of the Chunk to be allocated.
203 * @return Chunk of memory. */
204 Chunk allocate(size_t chunk_size) noexcept;
205
206 /** Deallocate a Chunk from a Block.
207 *
208 * [in] Chunk to be deallocated.
209 * [in] Size of the Chunk to be deallocated.
210 * @return Remaining number of Chunks in a Block. */
211 size_t deallocate(Chunk chunk, size_t chunk_size) noexcept;
212
213 /** Destroy the whole Block. This operation will release all occupied memory
214 * by the Block so client code must make sure that it doesn't keep dangling
215 * Chunks in the memory. */
216 void destroy() noexcept;
217
218 /** Check if Block is empty (not holding any data).
219 *
220 * @return true if it is */
221 bool is_empty() const;
222
223 /** Check if Block can fit (allocate) a Chunk of given size.
224 *
225 * [in] Desired chunk size in bytes.
226 * @return true if can */
227 bool can_accommodate(size_t chunk_size) const;
228
229 /** Get the Block Source type (memory where it resides).
230 *
231 * @return one of Source values */
232 Source type() const;
233
234 /** Get the Block size.
235 *
236 * @return Block size */
237 size_t size() const;
238
239 /** Get current number of Chunks allocated by the Block.
240 *
241 * @return Number of Chunks allocated by this Block */
242 size_t number_of_used_chunks() const;
243
244 /** A human-readable string that describes a Block.
245 *
246 * @return human-readable string */
247 std::string to_string() const;
248
249 /** For given size, how much memory will Block with single Chunk actually
250 * occupy. This calculation takes into account both the Header/Chunk metadata
251 * and the data payload.
252 *
253 * [in] Data payload size in bytes.
254 * @return Size Block would allocate for given n_bytes.
255 * */
256 static size_t size_hint(size_t n_bytes);
257
258 private:
259 /** Delegating constructor which populates Header with provided information.
260 *
261 * [in] Address of a memory region allocated by the Block
262 * [in] Source of memory region
263 * [in] Block size in bytes. */
264 Block(uint8_t *block_memory, Source block_type, size_t block_size) noexcept;
265
266 /** Are we looking at the last (rightmost) chunk in a Block.
267 *
268 * [in] Reference to a Chunk
269 * [in] Chunk size
270 * @return true if we are */
271 bool is_rightmost_chunk(const Chunk &chunk, size_t chunk_size) const;
272
273 /** What is the word-size (ALIGN_TO) aligned size of an input size?
274 *
275 * [in] Input size
276 * @return Block-size rounded up to the next ALIGN_TO size */
277 static size_t aligned_size(size_t size);
278};
279
280static inline uint8_t *allocate_from(Source src, size_t size) {
281 void *ptr = nullptr;
282 size_t raw_size = size;
283#ifdef HAVE_PSI_MEMORY_INTERFACE
284 raw_size += PSI_HEADER_SIZE;
285#endif
286 if (src == Source::RAM) {
287 ptr = Memory<Source::RAM>::allocate(raw_size);
289 } else if (src == Source::MMAP_FILE) {
292 }
293#ifdef HAVE_PSI_MEMORY_INTERFACE
294 return reinterpret_cast<uint8_t *>(HEADER_TO_USER(ptr));
295#else
296 return reinterpret_cast<uint8_t *>(ptr);
297#endif
298}
299
300static inline void deallocate_from(Source src, size_t size,
301 uint8_t *block_address) {
302 size_t raw_size = size;
303 uint8_t *raw_block_address = block_address;
304#ifdef HAVE_PSI_MEMORY_INTERFACE
305 raw_size += PSI_HEADER_SIZE;
306 raw_block_address = USER_TO_HEADER_UINT8_T(block_address);
307#endif
308 if (src == Source::RAM) {
310 Memory<Source::RAM>::deallocate(raw_block_address, raw_size);
311 } else if (src == Source::MMAP_FILE) {
313 Memory<Source::MMAP_FILE>::deallocate(raw_block_address, raw_size);
314 }
315}
316
317inline Block::Block(Chunk chunk) noexcept : Header(chunk.block()) {
318 assert(!is_empty());
319}
320
321inline Block::Block(size_t size, Source memory_source)
322 : Block(allocate_from(memory_source, Block::aligned_size(size)),
323 memory_source, Block::aligned_size(size)) {
324 assert(!is_empty());
325}
326
327inline Block::Block(uint8_t *block_memory, Source block_memory_type,
328 size_t block_size) noexcept
329 : Header(block_memory, block_memory_type, block_size) {
330 assert(!is_empty());
331
332 /* Prevent writes to the memory which we took from the OS but still have
333 * not shipped outside of the Allocator. This will also prevent reads, but
334 * reads would have been reported even without this because the memory we
335 * took from the OS is "undefined" by default. */
337 block_size - Header::SIZE);
338
339 DBUG_PRINT("temptable_allocator", ("block create: size=%zu, new_block=(%s)",
340 block_size, to_string().c_str()));
341}
342
343inline bool Block::operator==(const Block &other) const {
344 return Header::block_address() == other.block_address();
345}
346
347inline bool Block::operator!=(const Block &other) const {
348 return !(Header::block_address() == other.block_address());
349}
350
351inline Chunk Block::allocate(size_t chunk_size) noexcept {
352 assert(!is_empty());
353 assert(can_accommodate(chunk_size));
354
355 const size_t chunk_size_aligned = Block::aligned_size(chunk_size);
356
357 /* Remove the "no access" flag we set on this memory during block
358 * creation. Relax it to report read+depend_on_contents. */
360 Chunk::size_hint(chunk_size_aligned));
361
364
365 Block_PSI_track_logical_allocation(chunk_size_aligned);
366 DBUG_PRINT("temptable_allocator",
367 ("allocate from block: chunk_size=%zu, from_block=(%s); "
368 "return=%p",
369 chunk_size, to_string().c_str(), chunk.data()));
370
371 return chunk;
372}
373
374inline size_t Block::deallocate(Chunk chunk, size_t chunk_size) noexcept {
375 assert(!is_empty());
376 DBUG_PRINT("temptable_allocator",
377 ("deallocate from block: size=%zu, from_block=(%s), chunk_data=%p",
378 chunk_size, to_string().c_str(), chunk.data()));
379
380 const size_t chunk_size_aligned = Block::aligned_size(chunk_size);
381 Block_PSI_track_logical_deallocation(chunk_size_aligned);
382
384 Chunk::size_hint(chunk_size_aligned),
385 is_rightmost_chunk(chunk, Chunk::size_hint(chunk_size_aligned)));
386}
387
388inline void Block::destroy() noexcept {
389 assert(!is_empty());
390 assert(Header::number_of_used_chunks() == 0);
391 DBUG_PRINT("temptable_allocator",
392 ("destroying the block: (%s)", to_string().c_str()));
393
397}
398
399inline bool Block::is_empty() const {
400 return Header::block_address() == nullptr;
401}
402
403inline bool Block::can_accommodate(size_t n_bytes) const {
404 assert(!is_empty());
405
406 const size_t n_bytes_aligned = Block::aligned_size(n_bytes);
407 const size_t block_size = Header::block_size();
409 assert(first_pristine_offset <=
410 std::numeric_limits<decltype(block_size)>::max() -
411 Chunk::size_hint(n_bytes_aligned));
412
413 return first_pristine_offset + Chunk::size_hint(n_bytes_aligned) <=
415}
416
417inline Source Block::type() const {
418 assert(!is_empty());
420}
421
422inline size_t Block::size() const {
423 assert(!is_empty());
424 return Header::block_size();
425}
426
427inline size_t Block::number_of_used_chunks() const {
428 assert(!is_empty());
430}
431
432inline std::string Block::to_string() const {
433 assert(!is_empty());
434 std::stringstream s;
435 s << "address=" << static_cast<void *>(Header::block_address())
436 << ", size=" << Header::block_size()
437 << ", num_chunks=" << Header::number_of_used_chunks()
438 << ", first_pristine=" << Header::first_pristine_offset();
439 return s.str();
440}
441
442inline bool Block::is_rightmost_chunk(const Chunk &chunk,
443 size_t size_bytes) const {
444 assert(!is_empty());
445 return chunk.offset() + size_bytes == Header::first_pristine_offset();
446}
447
448inline size_t Block::size_hint(size_t n_bytes) {
450}
451
452inline size_t Block::aligned_size(size_t size) {
453 return (size + Block::ALIGN_TO - 1) & ~(Block::ALIGN_TO - 1);
454}
455
456} /* namespace temptable */
457
458#endif /* TEMPTABLE_BLOCK_H */
Chunk abstraction for temptable Block allocator.
Memory-block abstraction whose purpose is to serve as a building block for custom memory-allocator im...
Definition: block.h:163
Source type() const
Get the Block Source type (memory where it resides).
Definition: block.h:417
bool is_empty() const
Check if Block is empty (not holding any data).
Definition: block.h:399
std::string to_string() const
A human-readable string that describes a Block.
Definition: block.h:432
void destroy() noexcept
Destroy the whole Block.
Definition: block.h:388
size_t deallocate(Chunk chunk, size_t chunk_size) noexcept
Deallocate a Chunk from a Block.
Definition: block.h:374
bool can_accommodate(size_t chunk_size) const
Check if Block can fit (allocate) a Chunk of given size.
Definition: block.h:403
size_t number_of_used_chunks() const
Get current number of Chunks allocated by the Block.
Definition: block.h:427
static constexpr size_t ALIGN_TO
Block will self-adjust all requested allocation-sizes to the multiple of this value.
Definition: block.h:167
size_t size() const
Get the Block size.
Definition: block.h:422
Block() noexcept=default
Default constructor which creates an empty Block.
bool operator==(const Block &other) const
Equality operator.
Definition: block.h:343
bool operator!=(const Block &other) const
Inequality operator.
Definition: block.h:347
Chunk allocate(size_t chunk_size) noexcept
Allocate a Chunk from a Block.
Definition: block.h:351
bool is_rightmost_chunk(const Chunk &chunk, size_t chunk_size) const
Are we looking at the last (rightmost) chunk in a Block.
Definition: block.h:442
static size_t aligned_size(size_t size)
What is the word-size (ALIGN_TO) aligned size of an input size?
Definition: block.h:452
static size_t size_hint(size_t n_bytes)
For given size, how much memory will Block with single Chunk actually occupy.
Definition: block.h:448
Chunk is an abstraction with the purpose of representing a smallest logical memory-unit within the Bl...
Definition: chunk.h:68
size_t offset() const
Get the Chunk offset relative to the start of belonging Block.
Definition: chunk.h:154
static size_t size_hint(size_t n_bytes)
For given size, how much memory will be occupied by the Chunk.
Definition: chunk.h:160
Header is an abstraction with the purpose of holding and maintaining the Block metadata.
Definition: header.h:74
uint8_t * next_available_slot() const
Enable Block to get the next available slot that it can use for next Chunk allocation.
Definition: header.h:191
size_t block_size() const
Get the Block size.
Definition: header.h:201
void reset()
Enable Block to reset the Header metadata upon Block destruction.
Definition: header.h:236
Source memory_source_type() const
Get the Block Source type (memory where it resides).
Definition: header.h:197
Header() noexcept
Default constructor which creates an empty Header.
Definition: header.h:173
uint8_t * block_address() const
Enable Block to get its memory address.
Definition: header.h:195
size_t increment_number_of_used_chunks(size_t chunk_size)
Enable Block to increment the reference-count when (logically) allocating new Chunks.
Definition: header.h:213
static constexpr size_t SIZE
Block header (metadata) size.
Definition: header.h:80
size_t decrement_number_of_used_chunks(size_t chunk_size, bool rightmost_chunk)
Enable Block to decrement the reference-count when (logically) deallocating existing Chunks.
Definition: header.h:218
size_t first_pristine_offset() const
Get current first-pristine-offset.
Definition: header.h:209
size_t number_of_used_chunks() const
Get current number of Chunks allocated by the Block.
Definition: header.h:205
#define USER_TO_HEADER_UINT8_T(P)
Definition: mysql_memory.h:96
#define PSI_HEADER_SIZE
Definition: mysql_memory.h:86
Header abstraction for temptable Block allocator.
static std::string to_string(const LEX_STRING &str)
Definition: lex_string.h:50
Various macros useful for communicating with memory debuggers, such as Valgrind.
#define MEM_NOACCESS(a, len)
Definition: memory_debugging.h:60
#define MEM_UNDEFINED(a, len)
Definition: memory_debugging.h:58
Memory utilities for temptable-allocator.
#define DBUG_PRINT(keyword, arglist)
Definition: my_dbug.h:181
#define HEADER_TO_USER(P)
Definition: my_memory.cc:47
Instrumentation helpers for memory allocation.
size_t size(const char *const c)
Definition: base64.h:46
Definition: gcs_xcom_synode.h:64
Definition: allocator.h:48
static void deallocate_from(Source src, size_t size, uint8_t *block_address)
Definition: block.h:300
void Block_PSI_init()
Initialize the PSI memory engine.
Definition: block.cc:74
void Block_PSI_track_physical_ram_allocation(void *ptr, size_t size)
Log physical memory allocation of a Block located in RAM.
Definition: block.cc:103
void Block_PSI_track_physical_disk_allocation(void *ptr, size_t size)
Log physical memory allocation of a Block located in MMAP-ed file.
Definition: block.cc:126
void Block_PSI_track_physical_ram_deallocation(uint8_t *ptr)
Log physical memory deallocation of a Block located in RAM.
Definition: block.cc:116
static uint8_t * allocate_from(Source src, size_t size)
Definition: block.h:280
Source
Type of memory allocated.
Definition: memutils.h:68
@ MMAP_FILE
Memory is allocated on disk, using mmap()'ed file.
@ RAM
Memory is allocated from RAM, using malloc() for example.
void Block_PSI_track_physical_disk_deallocation(uint8_t *ptr)
Log physical memory deallocation of a Block located in MMAP-ed file.
Definition: block.cc:139
void Block_PSI_track_logical_deallocation(size_t size)
Log logical (Chunk) memory deallocation.
Definition: block.cc:95
void Block_PSI_track_logical_allocation(size_t size)
Log logical (Chunk) memory allocation.
Definition: block.cc:81
static void deallocate(void *ptr, size_t bytes)
static void * allocate(size_t bytes)