Buckets:
| /* ---------------------------------------------------------------------------- | |
| Copyright (c) 2018-2026, Microsoft Research, Daan Leijen | |
| This is free software; you can redistribute it and/or modify it under the | |
| terms of the MIT license. A copy of the license can be found in the file | |
| "LICENSE" at the root of this distribution. | |
| -----------------------------------------------------------------------------*/ | |
| // ------------------------------------------------------ | |
| // Compiler specific attributes | |
| // ------------------------------------------------------ | |
| // ------------------------------------------------------ | |
| // Includes | |
| // ------------------------------------------------------ | |
| extern "C" { | |
| // ------------------------------------------------------ | |
| // Standard malloc interface | |
| // ------------------------------------------------------ | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc(size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); | |
| mi_decl_nodiscard mi_decl_export void* mi_realloc(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); | |
| mi_decl_export void* mi_expand(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); | |
| mi_decl_export void mi_free(void* p) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strdup(const char* s) mi_attr_noexcept mi_attr_malloc; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_strndup(const char* s, size_t n) mi_attr_noexcept mi_attr_malloc; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_realpath(const char* fname, char* resolved_name) mi_attr_noexcept; | |
| // ------------------------------------------------------ | |
| // Extended allocation functions | |
| // ------------------------------------------------------ | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_small(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_mallocn(size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); | |
| mi_decl_nodiscard mi_decl_export void* mi_reallocn(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); | |
| mi_decl_nodiscard mi_decl_export void* mi_reallocf(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export size_t mi_usable_size(const void* p) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export size_t mi_good_size(size_t size) mi_attr_noexcept; | |
| // `mi_free_small` is for special applications like language runtimes. | |
| // it should only be used to free objects from `mi_(heap_)(m|z)alloc_small` and is potentially a tiny bit faster than `mi_free` | |
| mi_decl_export void mi_free_small(void* p) mi_attr_noexcept; | |
| // ------------------------------------------------------------------------------------- | |
| // Aligned allocation | |
| // Note that `alignment` always follows `size` for consistency with unaligned | |
| // allocation, but unfortunately this differs from `posix_memalign` and `aligned_alloc`. | |
| // ------------------------------------------------------------------------------------- | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_malloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_zalloc_aligned_at(size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned(size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1, 2) mi_attr_alloc_align(3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_calloc_aligned_at(size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1, 2); | |
| mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3); | |
| mi_decl_nodiscard mi_decl_export void* mi_realloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2); | |
| // ------------------------------------------------------ | |
| // Typed allocation, the type is always the first parameter | |
| // ------------------------------------------------------ | |
| // ------------------------------------------------------ | |
| // Internals | |
| // See also `mimalloc-stats.h` for statistics | |
| // ------------------------------------------------------ | |
| typedef void (mi_cdecl mi_deferred_free_fun)(bool force, unsigned long long heartbeat, void* arg); | |
| mi_decl_export void mi_register_deferred_free(mi_deferred_free_fun* deferred_free, void* arg) mi_attr_noexcept; | |
| typedef void (mi_cdecl mi_output_fun)(const char* msg, void* arg); | |
| mi_decl_export void mi_register_output(mi_output_fun* out, void* arg) mi_attr_noexcept; | |
| typedef void (mi_cdecl mi_error_fun)(int err, void* arg); | |
| mi_decl_export void mi_register_error(mi_error_fun* fun, void* arg); | |
| mi_decl_export void mi_collect(bool force) mi_attr_noexcept; | |
| mi_decl_export int mi_version(void) mi_attr_noexcept; | |
| mi_decl_export void mi_options_print(void) mi_attr_noexcept; | |
| mi_decl_export void mi_process_info_print(void) mi_attr_noexcept; | |
| mi_decl_export void mi_options_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; | |
| mi_decl_export void mi_process_info_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; | |
| mi_decl_export void mi_process_info(size_t* elapsed_msecs, size_t* user_msecs, size_t* system_msecs, | |
| size_t* current_rss, size_t* peak_rss, | |
| size_t* current_commit, size_t* peak_commit, size_t* page_faults) mi_attr_noexcept; | |
| // Generally do not use the following as these are usually called automatically | |
| mi_decl_export void mi_process_init(void) mi_attr_noexcept; | |
| mi_decl_export void mi_cdecl mi_process_done(void) mi_attr_noexcept; | |
| mi_decl_export void mi_thread_init(void) mi_attr_noexcept; | |
| mi_decl_export void mi_thread_done(void) mi_attr_noexcept; | |
| mi_decl_export void mi_thread_set_in_threadpool(void) mi_attr_noexcept; // communicate that a thread is in a threadpool | |
| // ----------------------------------------------------------------- | |
| // Return allocated block size (if the return value is not NULL) | |
| // ----------------------------------------------------------------- | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_ucalloc(size_t count, size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(1,2); | |
| mi_decl_nodiscard mi_decl_export void* mi_urealloc(void* p, size_t newsize, size_t* block_size_pre, size_t* block_size_post) mi_attr_noexcept mi_attr_alloc_size(2); | |
| mi_decl_export void mi_ufree(void* p, size_t* block_size) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_uzalloc_aligned(size_t size, size_t alignment, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_umalloc_small(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_uzalloc_small(size_t size, size_t* block_size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| // ------------------------------------------------------------------------------------- | |
| // Heaps: first-class. Can allocate from any thread (and be free'd from any thread) | |
| // Heaps keep allocations in separate pages from each other (but share the arena's and free'd pages) | |
| // ------------------------------------------------------------------------------------- | |
| struct mi_heap_s; | |
| typedef struct mi_heap_s mi_heap_t; | |
| mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new(void); | |
| mi_decl_export void mi_heap_delete(mi_heap_t* heap); // move live blocks to the main heap | |
| mi_decl_export void mi_heap_destroy(mi_heap_t* heap); // free all live blocks | |
| mi_decl_export void mi_heap_set_numa_affinity(mi_heap_t* heap, int numa_node); | |
| mi_decl_export void mi_heap_collect(mi_heap_t* heap, bool force); | |
| mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_main(void); | |
| mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_of(const void* p); | |
| mi_decl_nodiscard mi_decl_export bool mi_heap_contains(const mi_heap_t* heap, const void* p); | |
| mi_decl_nodiscard mi_decl_export bool mi_any_heap_contains(const void* p); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc(mi_heap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_mallocn(mi_heap_t* heap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_small(mi_heap_t* heap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_realloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_reallocn(mi_heap_t* heap, void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(3, 4); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_reallocf(mi_heap_t* theap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strdup(mi_heap_t* heap, const char* s) mi_attr_noexcept mi_attr_malloc; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_strndup(mi_heap_t* heap, const char* s, size_t n) mi_attr_noexcept mi_attr_malloc; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict char* mi_heap_realpath(mi_heap_t* heap, const char* fname, char* resolved_name) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_malloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned(mi_heap_t* heap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_zalloc_aligned_at(mi_heap_t* heap, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned(mi_heap_t* heap, size_t count, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3) mi_attr_alloc_align(4); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_calloc_aligned_at(mi_heap_t* heap, size_t count, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_realloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3); | |
| // -------------------------------------------------------------------------------- | |
| // Zero initialized re-allocation. | |
| // Only valid on memory that was originally allocated with zero initialization too. | |
| // e.g. `mi_calloc`, `mi_zalloc`, `mi_zalloc_aligned` etc. | |
| // see <https://github.com/microsoft/mimalloc/issues/63#issuecomment-508272992> | |
| // -------------------------------------------------------------------------------- | |
| mi_decl_nodiscard mi_decl_export void* mi_rezalloc(void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export void* mi_recalloc(void* p, size_t newcount, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); | |
| mi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned(void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(2) mi_attr_alloc_align(3); | |
| mi_decl_nodiscard mi_decl_export void* mi_rezalloc_aligned_at(void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(2,3) mi_attr_alloc_align(4); | |
| mi_decl_nodiscard mi_decl_export void* mi_recalloc_aligned_at(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(2,3); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc(mi_heap_t* heap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc(mi_heap_t* heap, void* p, size_t newcount, size_t size) mi_attr_noexcept mi_attr_alloc_size2(3, 4); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned(mi_heap_t* heap, void* p, size_t newsize, size_t alignment) mi_attr_noexcept mi_attr_alloc_size(3) mi_attr_alloc_align(4); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_rezalloc_aligned_at(mi_heap_t* heap, void* p, size_t newsize, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size(3); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept mi_attr_alloc_size2(3, 4) mi_attr_alloc_align(5); | |
| mi_decl_nodiscard mi_decl_export void* mi_heap_recalloc_aligned_at(mi_heap_t* heap, void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept mi_attr_alloc_size2(3, 4); | |
| // ------------------------------------------------------ | |
| // Visiting pages and individual blocks in a heap. | |
| // ------------------------------------------------------ | |
| // An area of heap space contains blocks of a single size. | |
| typedef struct mi_heap_area_s { | |
| void* blocks; // start of the area containing theap blocks | |
| size_t reserved; // bytes reserved for this area (virtual) | |
| size_t committed; // current available bytes for this area | |
| size_t used; // number of allocated blocks | |
| size_t block_size; // size in bytes of each block | |
| size_t full_block_size; // size in bytes of a full block including padding and metadata. | |
| void* reserved1; // internal | |
| } mi_heap_area_t; | |
| typedef bool (mi_cdecl mi_block_visit_fun)(const mi_heap_t* heap, const mi_heap_area_t* area, void* block, size_t block_size, void* arg); | |
| mi_decl_export bool mi_heap_visit_blocks(mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg); | |
| mi_decl_export bool mi_heap_visit_abandoned_blocks(mi_heap_t* heap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg); | |
| // ------------------------------------------------------ | |
| // Arena memory management | |
| // Arena's are larger memory area's provided by the OS or user | |
| // ------------------------------------------------------ | |
| mi_decl_nodiscard mi_decl_export bool mi_is_redirected(void) mi_attr_noexcept; | |
| mi_decl_export int mi_reserve_huge_os_pages_interleave(size_t pages, size_t numa_nodes, size_t timeout_msecs) mi_attr_noexcept; | |
| mi_decl_export int mi_reserve_huge_os_pages_at(size_t pages, int numa_node, size_t timeout_msecs) mi_attr_noexcept; | |
| mi_decl_export int mi_reserve_os_memory(size_t size, bool commit, bool allow_large) mi_attr_noexcept; | |
| mi_decl_export bool mi_manage_os_memory(void* start, size_t size, bool is_committed, bool is_pinned /* cannot decommit/reset? */, bool is_zero, int numa_node) mi_attr_noexcept; | |
| mi_decl_export void mi_debug_show_arenas(void) mi_attr_noexcept; | |
| mi_decl_export void mi_arenas_print(void) mi_attr_noexcept; | |
| mi_decl_export size_t mi_arena_min_alignment(void); | |
| mi_decl_export size_t mi_arena_min_size(void); | |
| typedef void* mi_arena_id_t; | |
| mi_decl_export void* mi_arena_area(mi_arena_id_t arena_id, size_t* size); | |
| mi_decl_export int mi_reserve_huge_os_pages_at_ex(size_t pages, int numa_node, size_t timeout_msecs, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept; | |
| mi_decl_export int mi_reserve_os_memory_ex(size_t size, bool commit, bool allow_large, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept; | |
| mi_decl_export bool mi_manage_os_memory_ex(void* start, size_t size, bool is_committed, bool is_pinned, bool is_zero, int numa_node, bool exclusive, mi_arena_id_t* arena_id) mi_attr_noexcept; | |
| mi_decl_export bool mi_arena_contains(mi_arena_id_t arena_id, const void* p); | |
| // Create a heap that only allocates in the specified arena | |
| mi_decl_nodiscard mi_decl_export mi_heap_t* mi_heap_new_in_arena(mi_arena_id_t arena_id); | |
| // ------------------------------------------------------ | |
| // Subprocesses | |
| // Advanced: allow sub-processes whose memory arena's stay fully separated (and no reclamation between them). | |
| // Used for example for separate interpreters in one process. | |
| // ------------------------------------------------------ | |
| typedef void* mi_subproc_id_t; | |
| mi_decl_export mi_subproc_id_t mi_subproc_main(void); | |
| mi_decl_export mi_subproc_id_t mi_subproc_current(void); | |
| mi_decl_export mi_subproc_id_t mi_subproc_new(void); | |
| mi_decl_export void mi_subproc_destroy(mi_subproc_id_t subproc); | |
| mi_decl_export void mi_subproc_add_current_thread(mi_subproc_id_t subproc); // this should be called right after a thread is created (and no allocation has taken place yet) | |
| typedef bool (mi_cdecl mi_heap_visit_fun)(mi_heap_t* heap, void* arg); | |
| mi_decl_export bool mi_subproc_visit_heaps(mi_subproc_id_t subproc, mi_heap_visit_fun* visitor, void* arg); | |
| // ------------------------------------------------------------------------------------- | |
| // A "theap" is a thread-local heap. This API is only provided for special circumstances like runtimes | |
| // that already have a thread-local context and can store the theap there for (slightly) faster allocations. | |
| // This also allows to set a default theap for the current thread so that `malloc` etc. allocate from | |
| // that theap (instead of the main (t)heap). | |
| // Theaps are first-class, but can only allocate from the same thread that created it. | |
| // Allocation through a `theap` may be a tiny bit faster than using plain malloc | |
| // (as we don't need to lookup the thread local variable). | |
| // ------------------------------------------------------------------------------------- | |
| struct mi_theap_s; | |
| typedef struct mi_theap_s mi_theap_t; | |
| mi_decl_export mi_theap_t* mi_heap_theap(mi_heap_t* heap); | |
| mi_decl_export mi_theap_t* mi_theap_set_default(mi_theap_t* theap); | |
| mi_decl_export mi_theap_t* mi_theap_get_default(void); | |
| mi_decl_export void mi_theap_collect(mi_theap_t* theap, bool force) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_malloc(mi_theap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_zalloc(mi_theap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_calloc(mi_theap_t* theap, size_t count, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size2(2, 3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_malloc_small(mi_theap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_zalloc_small(mi_theap_t* theap, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_theap_malloc_aligned(mi_theap_t* theap, size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(3); | |
| mi_decl_nodiscard mi_decl_export void* mi_theap_realloc(mi_theap_t* theap, void* p, size_t newsize) mi_attr_noexcept mi_attr_alloc_size(3); | |
| // ------------------------------------------------------ | |
| // Experimental | |
| // ------------------------------------------------------ | |
| // Experimental: objects followed by a guard page. | |
| // Setting the sample rate on a specific theap can be used to test parts of the program more | |
| // specifically (in combination with `mi_theap_set_default`). | |
| // A sample rate of 0 disables guarded objects, while 1 uses a guard page for every object. | |
| // A seed of 0 uses a random start point. Only objects within the size bound are eligable for guard pages. | |
| mi_decl_export void mi_theap_guarded_set_sample_rate(mi_theap_t* theap, size_t sample_rate, size_t seed); | |
| mi_decl_export void mi_theap_guarded_set_size_bound(mi_theap_t* theap, size_t min, size_t max); | |
| // very experimental | |
| typedef bool (mi_cdecl mi_commit_fun_t)(bool commit, void* start, size_t size, bool* is_zero, void* user_arg); | |
| mi_decl_export bool mi_manage_memory(void* start, size_t size, bool is_committed, bool is_pinned, bool is_zero, int numa_node, bool exclusive, | |
| mi_commit_fun_t* commit_fun, void* commit_fun_arg, mi_arena_id_t* arena_id) mi_attr_noexcept; | |
| //mi_decl_export bool mi_arena_unload(mi_arena_id_t arena_id, void** base, size_t* accessed_size, size_t* size); | |
| //mi_decl_export bool mi_arena_reload(void* start, size_t size, mi_commit_fun_t* commit_fun, void* commit_fun_arg, mi_arena_id_t* arena_id); | |
| //mi_decl_export bool mi_theap_reload(mi_theap_t* theap, mi_arena_id_t arena); | |
| //mi_decl_export void mi_theap_unload(mi_theap_t* theap); | |
| // unsafe: assumes the page belonging to `p` is only accessed by the calling thread. | |
| mi_decl_export bool mi_unsafe_heap_page_is_under_utilized(mi_heap_t* heap, void* p, size_t perc_threshold) mi_attr_noexcept; | |
| // ------------------------------------------------------ | |
| // Deprecated | |
| // ------------------------------------------------------ | |
| mi_decl_export bool mi_check_owned(const void* p); | |
| mi_decl_export void mi_thread_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export bool mi_is_in_heap_region(const void* p) mi_attr_noexcept; | |
| mi_decl_export bool mi_theap_visit_blocks(const mi_theap_t* theap, bool visit_blocks, mi_block_visit_fun* visitor, void* arg); | |
| mi_decl_export int mi_reserve_huge_os_pages(size_t pages, double max_secs, size_t* pages_reserved) mi_attr_noexcept; | |
| mi_decl_export void mi_collect_reduce(size_t target_thread_owned) mi_attr_noexcept; | |
| mi_decl_export void mi_stats_reset(void) mi_attr_noexcept; | |
| mi_decl_export void mi_stats_merge(void) mi_attr_noexcept; | |
| mi_decl_export void mi_stats_print(void* out) mi_attr_noexcept; // backward compatibility: `out` is ignored and should be NULL | |
| mi_decl_export void mi_stats_print_out(mi_output_fun* out, void* arg) mi_attr_noexcept; // not deprecated but declared in `mimalloc-stats.h` now. | |
| // ------------------------------------------------------ | |
| // Options | |
| // ------------------------------------------------------ | |
| typedef enum mi_option_e { | |
| // stable options | |
| mi_option_show_errors, // print error messages | |
| mi_option_show_stats, // print statistics on termination | |
| mi_option_verbose, // print verbose messages | |
| // advanced options | |
| mi_option_deprecated_eager_commit, | |
| mi_option_arena_eager_commit, // eager commit arenas? Use 2 to enable just on overcommit systems (=2) | |
| mi_option_purge_decommits, // should a memory purge decommit? (=1). Set to 0 to use memory reset on a purge (instead of decommit) | |
| mi_option_allow_large_os_pages, // allow use of large (2 or 4 MiB) OS pages, implies eager commit. | |
| mi_option_reserve_huge_os_pages, // reserve N huge OS pages (1GiB pages) at startup | |
| mi_option_reserve_huge_os_pages_at, // reserve huge OS pages at a specific NUMA node | |
| mi_option_reserve_os_memory, // reserve specified amount of OS memory in an arena at startup (internally, this value is in KiB; use `mi_option_get_size`) | |
| mi_option_deprecated_segment_cache, | |
| mi_option_deprecated_page_reset, | |
| mi_option_deprecated_abandoned_page_purge, | |
| mi_option_deprecated_segment_reset, | |
| mi_option_deprecated_eager_commit_delay, | |
| mi_option_purge_delay, // memory purging is delayed by N milli seconds; use 0 for immediate purging or -1 for no purging at all. (=10) | |
| mi_option_use_numa_nodes, // 0 = use all available numa nodes, otherwise use at most N nodes. | |
| mi_option_disallow_os_alloc, // 1 = do not use OS memory for allocation (but only programmatically reserved arenas) | |
| mi_option_os_tag, // tag used for OS logging (macOS only for now) (=100) | |
| mi_option_max_errors, // issue at most N error messages | |
| mi_option_max_warnings, // issue at most N warning messages | |
| mi_option_deprecated_max_segment_reclaim, // max. percentage of the abandoned segments can be reclaimed per try (=10%) | |
| mi_option_destroy_on_exit, // if set, release all memory on exit; sometimes used for dynamic unloading but can be unsafe | |
| mi_option_arena_reserve, // initial memory size for arena reservation (= 1 GiB on 64-bit) (internally, this value is in KiB; use `mi_option_get_size`) | |
| mi_option_arena_purge_mult, // multiplier for `purge_delay` for the purging delay for arenas (=10) | |
| mi_option_deprecated_purge_extend_delay, | |
| mi_option_disallow_arena_alloc, // 1 = do not use arena's for allocation (except if using specific arena id's) | |
| mi_option_retry_on_oom, // retry on out-of-memory for N milli seconds (=400), set to 0 to disable retries. (only on windows) | |
| mi_option_visit_abandoned, // allow visiting theap blocks from abandoned threads (=0) | |
| mi_option_guarded_min, // only used when building with MI_GUARDED: minimal rounded object size for guarded objects (=0) | |
| mi_option_guarded_max, // only used when building with MI_GUARDED: maximal rounded object size for guarded objects (=0) | |
| mi_option_guarded_precise, // disregard minimal alignment requirement to always place guarded blocks exactly in front of a guard page (=0) | |
| mi_option_guarded_sample_rate, // 1 out of N allocations in the min/max range will be guarded (=1000) | |
| mi_option_guarded_sample_seed, // can be set to allow for a (more) deterministic re-execution when a guard page is triggered (=0) | |
| mi_option_generic_collect, // collect theaps every N (=10000) generic allocation calls | |
| mi_option_page_reclaim_on_free, // reclaim abandoned pages on a free (=0). -1 disallowr always, 0 allows if the page originated from the current theap, 1 allow always | |
| mi_option_page_full_retain, // retain N full (small) pages per size class (=2) | |
| mi_option_page_max_candidates, // max candidate pages to consider for allocation (=4) | |
| mi_option_max_vabits, // max user space virtual address bits to consider (=48) | |
| mi_option_pagemap_commit, // commit the full pagemap (to always catch invalid pointer uses) (=0) | |
| mi_option_page_commit_on_demand, // commit page memory on-demand | |
| mi_option_page_max_reclaim, // don't reclaim pages of the same originating theap if we already own N pages (in that size class) (=-1 (unlimited)) | |
| mi_option_page_cross_thread_max_reclaim, // don't reclaim pages across threads if we already own N pages (in that size class) (=16) | |
| mi_option_allow_thp, // allow transparent huge pages? (=1) (on Android =0 by default). Set to 0 to disable THP for the process. | |
| mi_option_minimal_purge_size, // set minimal purge size (in KiB) (=0). By default set to either 64 or 2048 if THP is enabled. | |
| mi_option_arena_max_object_size, // set maximal object size that can be allocated in an arena (in KiB) (=2GiB on 64-bit). | |
| _mi_option_last, | |
| // legacy option names | |
| mi_option_large_os_pages = mi_option_allow_large_os_pages, | |
| mi_option_eager_region_commit = mi_option_arena_eager_commit, | |
| mi_option_reset_decommits = mi_option_purge_decommits, | |
| mi_option_reset_delay = mi_option_purge_delay, | |
| mi_option_limit_os_alloc = mi_option_disallow_os_alloc | |
| } mi_option_t; | |
| mi_decl_nodiscard mi_decl_export bool mi_option_is_enabled(mi_option_t option); | |
| mi_decl_export void mi_option_enable(mi_option_t option); | |
| mi_decl_export void mi_option_disable(mi_option_t option); | |
| mi_decl_export void mi_option_set_enabled(mi_option_t option, bool enable); | |
| mi_decl_export void mi_option_set_enabled_default(mi_option_t option, bool enable); | |
| mi_decl_nodiscard mi_decl_export long mi_option_get(mi_option_t option); | |
| mi_decl_nodiscard mi_decl_export long mi_option_get_clamp(mi_option_t option, long min, long max); | |
| mi_decl_nodiscard mi_decl_export size_t mi_option_get_size(mi_option_t option); | |
| mi_decl_export void mi_option_set(mi_option_t option, long value); | |
| mi_decl_export void mi_option_set_default(mi_option_t option, long value); | |
| // ------------------------------------------------------------------------------------------------------- | |
| // "mi" prefixed implementations of various posix, Unix, Windows, and C++ allocation functions. | |
| // (This can be convenient when providing overrides of these functions as done in `mimalloc-override.h`.) | |
| // note: we use `mi_cfree` as "checked free" and it checks if the pointer is in our theap before free-ing. | |
| // ------------------------------------------------------------------------------------------------------- | |
| mi_decl_export void mi_cfree(void* p) mi_attr_noexcept; | |
| mi_decl_export void* mi__expand(void* p, size_t newsize) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export size_t mi_malloc_size(const void* p) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export size_t mi_malloc_good_size(size_t size) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export size_t mi_malloc_usable_size(const void *p) mi_attr_noexcept; | |
| mi_decl_export int mi_posix_memalign(void** p, size_t alignment, size_t size) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_memalign(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_valloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_pvalloc(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_aligned_alloc(size_t alignment, size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(2) mi_attr_alloc_align(1); | |
| mi_decl_nodiscard mi_decl_export void* mi_reallocarray(void* p, size_t count, size_t size) mi_attr_noexcept mi_attr_alloc_size2(2,3); | |
| mi_decl_nodiscard mi_decl_export int mi_reallocarr(void* ptrp, size_t count, size_t size) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export void* mi_aligned_recalloc(void* p, size_t newcount, size_t size, size_t alignment) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export void* mi_aligned_offset_recalloc(void* p, size_t newcount, size_t size, size_t alignment, size_t offset) mi_attr_noexcept; | |
| mi_decl_export void mi_free_size(void* p, size_t size) mi_attr_noexcept; | |
| mi_decl_export void mi_free_size_aligned(void* p, size_t size, size_t alignment) mi_attr_noexcept; | |
| mi_decl_export void mi_free_aligned(void* p, size_t alignment) mi_attr_noexcept; | |
| mi_decl_export int mi_dupenv_s(char** buf, size_t* size, const char* name) mi_attr_noexcept; | |
| // wide characters | |
| mi_decl_export int mi_wdupenv_s(wchar_t** buf, size_t* size, const wchar_t* name) mi_attr_noexcept; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict wchar_t* mi_wcsdup(const wchar_t* s) mi_attr_noexcept mi_attr_malloc; | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict unsigned char* mi_mbsdup(const unsigned char* s) mi_attr_noexcept mi_attr_malloc; | |
| // The `mi_new` wrappers implement C++ semantics on out-of-memory instead of directly returning `NULL`. | |
| // (and call `std::get_new_handler` and potentially raise a `std::bad_alloc` exception). | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new(size_t size) mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned(size_t size, size_t alignment) mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_nothrow(size_t size) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_aligned_nothrow(size_t size, size_t alignment) mi_attr_noexcept mi_attr_malloc mi_attr_alloc_size(1) mi_attr_alloc_align(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_new_n(size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(1, 2); | |
| mi_decl_nodiscard mi_decl_export void* mi_new_realloc(void* p, size_t newsize) mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export void* mi_new_reallocn(void* p, size_t newcount, size_t size) mi_attr_alloc_size2(2, 3); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new(mi_heap_t* heap, size_t size) mi_attr_malloc mi_attr_alloc_size(2); | |
| mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new_n(mi_heap_t* heap, size_t count, size_t size) mi_attr_malloc mi_attr_alloc_size2(2, 3); | |
| } | |
| // --------------------------------------------------------------------------------------------- | |
| // Implement the C++ std::allocator interface for use in STL containers. | |
| // (note: see `mimalloc-new-delete.h` for overriding the new/delete operators globally) | |
| // --------------------------------------------------------------------------------------------- | |
| template<class T> struct _mi_stl_allocator_common { | |
| typedef T value_type; | |
| typedef std::size_t size_type; | |
| typedef std::ptrdiff_t difference_type; | |
| typedef value_type& reference; | |
| typedef value_type const& const_reference; | |
| typedef value_type* pointer; | |
| typedef value_type const* const_pointer; | |
| using propagate_on_container_copy_assignment = std::true_type; | |
| using propagate_on_container_move_assignment = std::true_type; | |
| using propagate_on_container_swap = std::true_type; | |
| template <class U, class ...Args> void construct(U* p, Args&& ...args) { ::new(p) U(std::forward<Args>(args)...); } | |
| template <class U> void destroy(U* p) mi_attr_noexcept { p->~U(); } | |
| void construct(pointer p, value_type const& val) { ::new(p) value_type(val); } | |
| void destroy(pointer p) { p->~value_type(); } | |
| size_type max_size() const mi_attr_noexcept { return (PTRDIFF_MAX/sizeof(value_type)); } | |
| pointer address(reference x) const { return &x; } | |
| const_pointer address(const_reference x) const { return &x; } | |
| }; | |
| template<class T> struct mi_stl_allocator : public _mi_stl_allocator_common<T> { | |
| using typename _mi_stl_allocator_common<T>::size_type; | |
| using typename _mi_stl_allocator_common<T>::value_type; | |
| using typename _mi_stl_allocator_common<T>::pointer; | |
| template <class U> struct rebind { typedef mi_stl_allocator<U> other; }; | |
| mi_stl_allocator() mi_attr_noexcept = default; | |
| mi_stl_allocator(const mi_stl_allocator&) mi_attr_noexcept = default; | |
| template<class U> mi_stl_allocator(const mi_stl_allocator<U>&) mi_attr_noexcept { } | |
| mi_stl_allocator select_on_container_copy_construction() const { return *this; } | |
| void deallocate(T* p, size_type) { mi_free(p); } | |
| mi_decl_nodiscard T* allocate(size_type count) { return static_cast<T*>(mi_new_n(count, sizeof(T))); } | |
| mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); } | |
| mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast<pointer>(mi_new_n(count, sizeof(value_type))); } | |
| using is_always_equal = std::true_type; | |
| }; | |
| template<class T1,class T2> bool operator==(const mi_stl_allocator<T1>& , const mi_stl_allocator<T2>& ) mi_attr_noexcept { return true; } | |
| template<class T1,class T2> bool operator!=(const mi_stl_allocator<T1>& , const mi_stl_allocator<T2>& ) mi_attr_noexcept { return false; } | |
| // Common base class for STL allocators in a specific theap | |
| template<class T, bool _mi_destroy> struct _mi_heap_stl_allocator_common : public _mi_stl_allocator_common<T> { | |
| using typename _mi_stl_allocator_common<T>::size_type; | |
| using typename _mi_stl_allocator_common<T>::value_type; | |
| using typename _mi_stl_allocator_common<T>::pointer; | |
| _mi_heap_stl_allocator_common(mi_heap_t* hp) : heap(hp, [](mi_heap_t*) {}) {} /* will not delete nor destroy the passed in heap */ | |
| mi_decl_nodiscard T* allocate(size_type count) { return static_cast<T*>(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(T))); } | |
| mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); } | |
| mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast<pointer>(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(value_type))); } | |
| using is_always_equal = std::false_type; | |
| void collect(bool force) { mi_heap_collect(this->heap.get(), force); } | |
| template<class U> bool is_equal(const _mi_heap_stl_allocator_common<U, _mi_destroy>& x) const { return (this->heap == x.heap); } | |
| protected: | |
| std::shared_ptr<mi_heap_t> heap; | |
| template<class U, bool D> friend struct _mi_heap_stl_allocator_common; | |
| _mi_heap_stl_allocator_common() { | |
| mi_heap_t* hp = mi_heap_new(); | |
| this->heap.reset(hp, (_mi_destroy ? &heap_destroy : &heap_delete)); /* calls heap_delete/destroy when the refcount drops to zero */ | |
| } | |
| _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } | |
| template<class U> _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common<U, _mi_destroy>& x) mi_attr_noexcept : heap(x.heap) { } | |
| private: | |
| static void heap_delete(mi_heap_t* hp) { if (hp != NULL) { mi_heap_delete(hp); } } | |
| static void heap_destroy(mi_heap_t* hp) { if (hp != NULL) { mi_heap_destroy(hp); } } | |
| }; | |
| // STL allocator allocation in a specific heap | |
| template<class T> struct mi_heap_stl_allocator : public _mi_heap_stl_allocator_common<T, false> { | |
| using typename _mi_heap_stl_allocator_common<T, false>::size_type; | |
| mi_heap_stl_allocator() : _mi_heap_stl_allocator_common<T, false>() { } // creates fresh heap that is deleted when the destructor is called | |
| mi_heap_stl_allocator(mi_heap_t* hp) : _mi_heap_stl_allocator_common<T, false>(hp) { } // no delete nor destroy on the passed in heap | |
| template<class U> mi_heap_stl_allocator(const mi_heap_stl_allocator<U>& x) mi_attr_noexcept : _mi_heap_stl_allocator_common<T, false>(x) { } | |
| mi_heap_stl_allocator select_on_container_copy_construction() const { return *this; } | |
| void deallocate(T* p, size_type) { mi_free(p); } | |
| template<class U> struct rebind { typedef mi_heap_stl_allocator<U> other; }; | |
| }; | |
| template<class T1, class T2> bool operator==(const mi_heap_stl_allocator<T1>& x, const mi_heap_stl_allocator<T2>& y) mi_attr_noexcept { return (x.is_equal(y)); } | |
| template<class T1, class T2> bool operator!=(const mi_heap_stl_allocator<T1>& x, const mi_heap_stl_allocator<T2>& y) mi_attr_noexcept { return (!x.is_equal(y)); } | |
| // STL allocator allocation in a specific heap, where `free` does nothing and | |
| // the heap is destroyed in one go on destruction -- use with care! | |
| template<class T> struct mi_heap_destroy_stl_allocator : public _mi_heap_stl_allocator_common<T, true> { | |
| using typename _mi_heap_stl_allocator_common<T, true>::size_type; | |
| mi_heap_destroy_stl_allocator() : _mi_heap_stl_allocator_common<T, true>() { } // creates fresh heap that is destroyed when the destructor is called | |
| mi_heap_destroy_stl_allocator(mi_heap_t* hp) : _mi_heap_stl_allocator_common<T, true>(hp) { } // no delete nor destroy on the passed in heap | |
| template<class U> mi_heap_destroy_stl_allocator(const mi_heap_destroy_stl_allocator<U>& x) mi_attr_noexcept : _mi_heap_stl_allocator_common<T, true>(x) { } | |
| mi_heap_destroy_stl_allocator select_on_container_copy_construction() const { return *this; } | |
| void deallocate(T*, size_type) { /* do nothing as we destroy the heap on destruct. */ } | |
| template<class U> struct rebind { typedef mi_heap_destroy_stl_allocator<U> other; }; | |
| }; | |
| template<class T1, class T2> bool operator==(const mi_heap_destroy_stl_allocator<T1>& x, const mi_heap_destroy_stl_allocator<T2>& y) mi_attr_noexcept { return (x.is_equal(y)); } | |
| template<class T1, class T2> bool operator!=(const mi_heap_destroy_stl_allocator<T1>& x, const mi_heap_destroy_stl_allocator<T2>& y) mi_attr_noexcept { return (!x.is_equal(y)); } | |
Xet Storage Details
- Size:
- 47.6 kB
- Xet hash:
- 7a3b84f33664acb9ba66c6d56f1dc9c8a4b3b7653d0b331eed79aeebac07962b
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.