MariaDB is primarily written in C++. — or are we? Hah, you will be surprised with how little C++ features we use.
TODO: https://jira.mariadb.org/browse/MDEV-35460
TODO: explore the struct & specialized threads such as in Replication
MariaDB/server has many “levels” for allocating heap memory, not to mention individual optimizations such as the Tranx_node_allocator @ sql/semisync_master.h.
Despite the many levels, some code still uses malloc(size_t) or new for memory that are not expected to last long (as in how Garbage Collectors finds most objects die young).
If the SAFEMALLOC compile define is not enabled, sf_malloc() and sf_free() are simply macros that delegate to malloc()/free(). If it is, though, then this function is an interface to malloc(size_t)` that
- Requires the requested size to not exceed
SIZE_T_MAX - 2^24 - Adds canaries around the requested memory to detect buffer overruns
- Huh, sounds like the job of sanitizers if the platform doesn’t already have some built-in.
- Tracks some staticstics
- Fills the requested memory if the
TRASH_FREED_MEMORYcompile define is enabled – with0xA5bytes (¥) on allocation and0x8F(C1 SS3) on free.
Memory that are expected to last are allocated from this function or a higher level.
In addition to calling sf_malloc(), my_malloc() also:
- Allocates 1 byte if 0 bytes are requested
- Compatibility with size 0 should be the caller’s concern, in my opinion.
- Requires the requested size to not exceed
SIZE_T_MAX - 2^24 - Tracks memory usage for the Performance Schema
PSI_memory_keyis appearently a category ID in the memory use instrumenation.
- Recognizes flags:
MY_FAE: terminates the program on errorMY_WME: write an error message
MY_ZEROFILL: zero-initialize the requested memory- If not set, fills the requested memory if the
TRASH_FREED_MEMORYcompile define is enabled – with0xA5bytes (¥) on allocation and0x8F(C1 SS3) on free.
- If not set, fills the requested memory if the
(The keen-eyed will notice that a couple of bullet points are repeated from sf_malloc() if SAFEMALLOC is enabled.
my_free(void*) has fixed this repeated 0x8F-filling issue, though.)
MEM_ROOT functions (not instance methods) invokes my_malloc() & co. in blocks.
alloc_root() flags MY_WME for my_malloc(), but flags ME_FATAL instead of MY_FAE…?
This function also fills the – wait, it’s the third time I’m repeating this!
MEM_ROOT is intended for collections that free all of its elements at once.
This is why this no longer has a corresponding freeing function.
And last but not least, this templated instance method is a convenience that simply delegates to alloc_root() using its MEM_ROOT.
THD is a noteworthy grandchild of Query_arena, which also exports it to C as thd_alloc(const THD*, size_t) @ include/mysql/service_thd_alloc.h.
C++ has an Allocator concept that enables allocation customization for its collection classes.
The default allocator is std::allocator.
I wrote operator news, operator deletes and allocators for the specializations above as C++ Practice.
I have not yet published this file nor pitched it to the organization, though, because I doubt its adoption.
TODO: Surprisingly, we don’t use C++’s std::string here.
Yep, if not even std::string, let alone “the STL structures”.
For them, the differences look great summarized as tables.
| Variable-Size Fixed-Capacity Array | inplace_vector<T, …> (C++26) |
Bounds_checked_array @ sql/sql_array.h |
TODO |
|---|---|---|---|
| TODO: has no-exception and unchecked versions of insertion methods | |||
| Growing & Shrinking | |||
| Additional methods | Iterator, data & underlying buffer methods, empty (!size()), front & back |
TODO | TODO |
| Bounds Checking |
| Variable-size Variable-Capacity Array | std::vector<T, …> |
Dynamic_array<Elem> @ sql/sql_array.h (C++ wrapper) |
DYNAMIC_ARRAY @ include/my_sys.h (C) |
MEM_ROOT_DYNAMIC_ARRAY @ include/my_sys.h (C) |
|---|---|---|---|---|
| Element type | Any type argument – can be whole instances or non-owning pointers | ← same (no emplace element construction) | uchar* & size – somewhat inconvenient |
void* & size |
| Allocator | Optionally specified when constructing | same → | my_malloc() |
alloc_root() |
| Load Factor | 100% | same → | 100% | N/A: no random insert/delete functions |
| Size per grow | unspecified, commonly 100% | Optionally specified when constructing; default → 16 elements | Optionally specified when constructing; 0 → [[(8160 bytes ÷ bytes/element).truncate, 2× initial capacity].min, 16].max elements |
← same (ignored if the MY_BUFFER_NO_RESIZE flag is set, where resizing get & set outputs NULL & true (instead of false) respectively) |
| Default capacity | not configurable | Optionally specified when constructing; default → 16 elements | Specified when constructing | ← same |
| Initial static buffer | not configurable | Optionally for the initial capacity using alloc_root() |
Specified non-NULL when constructing | ← same |
| Additional methods | Iterator, data & underlying buffer methods, empty (!size()), front & back, push_back & pop_back, resize |
elements(size_t) (resize), find_first, front & back, push & pop, sort (QuickSort), underlying buffer methods |
alloc_dynamic (reserve one more element) & underlying buffer functions, delete_dynamic_with_callback (clear with destructor), pop_dynamic & push_dynamic, sort_dynamic (Quicksort) |
resizing get/set, mem_root_allocate_dynamic (reserve), mem_root_dynamic_array_copy_values, mem_root_dynamic_array_resize_not_allowed (set the MY_BUFFER_NO_RESIZE flag) |
| Bounds Checking | at: throws std::out_of_range; others: UB |
pop: outputs NULL; others: debug assertion fails |
get: outputs 0s; set: resize; pop: outputs NULL; others: OOB memory |
Debug assertion fails |
| Doubly Linked List | std::list<T, …> |
TODO | TODO |
|---|---|---|---|
| Allocator | Optionally specified when constructing | TODO | TODO |
| Additional methods | Iterator, empty (!size()), merge (for sorted lists), remove_if, resize, reverse, sort (≅Θ(N log N)), splice (move all of another list), unique (deduplicate) |
TODO | TODO |
| Bounds Checking | TODO | TODO | TODO |
| Singly linked version | std::forward_list<T, …> |
TODO | TODO |
| Sorted Tree Set/Map | |
|---|---|
| C++ | std::(multi_)set<Key, …>/map<Key, T, …> |
| MariaDB | Not available, it seems – use bsearch() on an array. |
| Unordered Hash Set/Map | std::(multi_)unordered_set<Key, …>/_map<Key, T, …> |
Hash_set<T> @ sql/sql_hset.h (C++ wrapper that’s seldom used) |
HASH @ include/hash.h (C) |
|---|---|---|---|
| Element type | Any type argument – can be whole instances or non-owning pointers | Pointer to any type argument | uchar*s of a charset specified when constructing (not even void*) – very inconvenient |
| Element destruction | Elements’ destructor (which does not destruct/free the data pointed by plain pointers) | same → | Specified when constructing; NULL → do nothing (does not free the pointed buffer) |
| Hash Maps | Separate classes, with map classes having separate key/value types | same → | Emulate from a set of key-value pairs by configuring a my_hash_get_key() (default: &element[key_offset]) |
| Multi-Set mode | Separate classes | same (default flagged) → | HASH_UNIQUE flag not set |
| Allocator | Optionally specified when constructing | same → | my_malloc() |
| Hash function | Optionally specified when constructing; default → std::hash() |
same → | Optionally specified when constructing; default or NULL → MY_COLLATION_HANDLER::hash_sort() |
| Key equality function | Optionally specified when constructing; default → operator== |
same → | MY_COLLATION_HANDLER::strnncoll() |
| Load Factor | Configurable via max_load_factor(float); default → 100% |
same → | Implemented with DYNAMIC_ARRAY, therefore 100% |
| Size per grow | probably 1× bucket_size() |
same → | Optionally specified when constructing; default or 0 → 8160 bytes (enough for over 500 entries!) |
| Default capacity if unspecified when constructing | unspecified | 8 elements | 0 |
| Additional methods | Iterator, empty (!size()), extract (get + delete), merge, underlying bucket methods |
Iterator, is_empty |
my_hash_iterate |
| Bounds Checking | unordered_map::operator[]: new insertion; unordered_map::at: throws std::out_of_range; others: UB |
same → | read: outputs NULL; delete: outputs true (instead of false) |
C++ Standard Library Resources @ cppreference.com: