LCOV - code coverage report
Current view: top level - src/base - memory_pool.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 459 472 97.2 %
Date: 2026-09-30 18:30:16 Functions: 39 39 100.0 %
Branches: 281 360 78.1 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * Copyright (C) 2026 by Thun Lu. All rights reserved.
       3                 :            :  * Author: Thun Lu <thun.lu@zohomail.cn>
       4                 :            :  * Repo:   https://github.com/thun-res/vlink
       5                 :            :  *  _    __   __      _           __
       6                 :            :  * | |  / /  / /     (_) ____    / /__
       7                 :            :  * | | / /  / /     / / / __ \  / //_/
       8                 :            :  * | |/ /  / /___  / / / / / / / ,<
       9                 :            :  * |___/  /_____/ /_/ /_/ /_/ /_/|_|
      10                 :            :  *
      11                 :            :  * Licensed under the Apache License, Version 2.0 (the "License");
      12                 :            :  * you may not use this file except in compliance with the License.
      13                 :            :  * You may obtain a copy of the License at
      14                 :            :  *
      15                 :            :  *     http://www.apache.org/licenses/LICENSE-2.0
      16                 :            :  *
      17                 :            :  * Unless required by applicable law or agreed to in writing, software
      18                 :            :  * distributed under the License is distributed on an "AS IS" BASIS,
      19                 :            :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      20                 :            :  * See the License for the specific language governing permissions and
      21                 :            :  * limitations under the License.
      22                 :            :  */
      23                 :            : 
      24                 :            : #include "./base/memory_pool.h"
      25                 :            : 
      26                 :            : #include <algorithm>
      27                 :            : #include <atomic>
      28                 :            : #include <charconv>
      29                 :            : #include <cstddef>
      30                 :            : #include <cstdint>
      31                 :            : #include <cstdio>
      32                 :            : #include <exception>
      33                 :            : #include <mutex>
      34                 :            : #include <new>
      35                 :            : #include <string>
      36                 :            : #include <system_error>
      37                 :            : #include <thread>
      38                 :            : #include <utility>
      39                 :            : #include <vector>
      40                 :            : 
      41                 :            : #include "./base/spin_lock.h"
      42                 :            : #include "./base/utils.h"
      43                 :            : 
      44                 :            : #define MEMORY_POOL_NEVER_DELETE 0
      45                 :            : 
      46                 :            : #define MEMORY_POOL_LOG(format, ...)                \
      47                 :            :   do {                                              \
      48                 :            :     std::fprintf(stderr, format "\n", __VA_ARGS__); \
      49                 :            :     std::fflush(stderr);                            \
      50                 :            :   } while (false)
      51                 :            : 
      52                 :            : namespace vlink {
      53                 :            : 
      54                 :            : static constexpr int kMinMemoryLevel = 0;
      55                 :            : static constexpr int kMaxMemoryLevel = 9;
      56                 :            : static constexpr int kDefaultMemoryLevel = 3;
      57                 :            : static constexpr size_t kMaxTierCount = 20U;
      58                 :            : static constexpr size_t kMaxLevelCount = 10U;
      59                 :            : static constexpr size_t kInitialBlocksPerChunk = 1U;
      60                 :            : static constexpr size_t kInitialChunksReserve = 16U;
      61                 :            : static constexpr size_t kInitialChunkBytesTarget = 64U * 1024U;
      62                 :            : static constexpr size_t kMaxLazyChunkBytes = 64U * 1024U;
      63                 :            : static constexpr size_t kMinLazyChunkBytes = 32U * 1024U;
      64                 :            : static constexpr size_t kLazyChunkQuotaDivisor = 16U;
      65                 :            : static constexpr size_t kMinTierShardCount = 8U;
      66                 :            : static constexpr size_t kMaxTierShardCount = 64U;
      67                 :            : static constexpr size_t kDefaultBatchSize = 16U;
      68                 :            : static constexpr uint32_t kShardingContentionThreshold = 8U;
      69                 :            : 
      70                 :            : static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ >= MemoryPool::kBlockAlignment,
      71                 :            :               "MemoryPool: plain operator new must satisfy kBlockAlignment");
      72                 :            : 
      73                 :            : // clang-format off
      74                 :            : static constexpr MemoryPool::Tier kDefaultTierTable[kMaxLevelCount][kMaxTierCount] = {
      75                 :            :     // L0 ~ 0 MiB. (bypass; all entries are sentinels)
      76                 :            :     {
      77                 :            :         {32U, 0U},
      78                 :            :         {64U, 0U},
      79                 :            :         {128U, 0U},
      80                 :            :         {256U, 0U},
      81                 :            :         {512U, 0U},
      82                 :            :         {1U * 1024U, 0U},
      83                 :            :         {2U * 1024U, 0U},
      84                 :            :         {4U * 1024U, 0U},
      85                 :            :         {8U * 1024U, 0U},
      86                 :            :         {16U * 1024U, 0U},
      87                 :            :         {32U * 1024U, 0U},
      88                 :            :         {64U * 1024U, 0U},
      89                 :            :         {128U * 1024U, 0U},
      90                 :            :         {256U * 1024U, 0U},
      91                 :            :         {512U * 1024U, 0U},
      92                 :            :         {1U * 1024U * 1024U, 0U},
      93                 :            :         {4U * 1024U * 1024U, 0U},
      94                 :            :         {8U * 1024U * 1024U, 0U},
      95                 :            :         {16U * 1024U * 1024U, 0U},
      96                 :            :     },
      97                 :            :     // L1 ~ 4 MiB.
      98                 :            :     {
      99                 :            :         {32U, 8U * 1024U},
     100                 :            :         {64U, 4U * 1024U},
     101                 :            :         {128U, 2U * 1024U},
     102                 :            :         {256U, 1U * 1024U},
     103                 :            :         {512U, 512U},
     104                 :            :         {1U * 1024U, 256U},
     105                 :            :         {2U * 1024U, 128U},
     106                 :            :         {4U * 1024U, 64U},
     107                 :            :         {8U * 1024U, 32U},
     108                 :            :         {16U * 1024U, 16U},
     109                 :            :         {32U * 1024U, 8U},
     110                 :            :         {64U * 1024U, 4U},
     111                 :            :         {128U * 1024U, 2U},
     112                 :            :         {256U * 1024U, 1U},
     113                 :            :         {512U * 1024U, 1U},
     114                 :            :         {1U * 1024U * 1024U, 0U},
     115                 :            :         {4U * 1024U * 1024U, 0U},
     116                 :            :         {8U * 1024U * 1024U, 0U},
     117                 :            :         {16U * 1024U * 1024U, 0U},
     118                 :            :     },
     119                 :            :     // L2 ~ 8.5 MiB.
     120                 :            :     {
     121                 :            :         {32U, 16U * 1024U},
     122                 :            :         {64U, 8U * 1024U},
     123                 :            :         {128U, 4U * 1024U},
     124                 :            :         {256U, 2U * 1024U},
     125                 :            :         {512U, 1U * 1024U},
     126                 :            :         {1U * 1024U, 512U},
     127                 :            :         {2U * 1024U, 256U},
     128                 :            :         {4U * 1024U, 128U},
     129                 :            :         {8U * 1024U, 64U},
     130                 :            :         {16U * 1024U, 32U},
     131                 :            :         {32U * 1024U, 16U},
     132                 :            :         {64U * 1024U, 8U},
     133                 :            :         {128U * 1024U, 4U},
     134                 :            :         {256U * 1024U, 2U},
     135                 :            :         {512U * 1024U, 1U},
     136                 :            :         {1U * 1024U * 1024U, 1U},
     137                 :            :         {4U * 1024U * 1024U, 0U},
     138                 :            :         {8U * 1024U * 1024U, 0U},
     139                 :            :         {16U * 1024U * 1024U, 0U},
     140                 :            :     },
     141                 :            :     // L3 ~ 16 MiB. (Default)
     142                 :            :     {
     143                 :            :         {32U, 32U * 1024U},
     144                 :            :         {64U, 16U * 1024U},
     145                 :            :         {128U, 8U * 1024U},
     146                 :            :         {256U, 4U * 1024U},
     147                 :            :         {512U, 2U * 1024U},
     148                 :            :         {1U * 1024U, 1U * 1024U},
     149                 :            :         {2U * 1024U, 512U},
     150                 :            :         {4U * 1024U, 256U},
     151                 :            :         {8U * 1024U, 128U},
     152                 :            :         {16U * 1024U, 64U},
     153                 :            :         {32U * 1024U, 32U},
     154                 :            :         {64U * 1024U, 16U},
     155                 :            :         {128U * 1024U, 8U},
     156                 :            :         {256U * 1024U, 4U},
     157                 :            :         {512U * 1024U, 2U},
     158                 :            :         {1U * 1024U * 1024U, 1U},
     159                 :            :         {4U * 1024U * 1024U, 0U},
     160                 :            :         {8U * 1024U * 1024U, 0U},
     161                 :            :         {16U * 1024U * 1024U, 0U},
     162                 :            :     },
     163                 :            :     // L4 ~ 42 MiB.
     164                 :            :     {
     165                 :            :         {32U, 64U * 1024U},
     166                 :            :         {64U, 32U * 1024U},
     167                 :            :         {128U, 16U * 1024U},
     168                 :            :         {256U, 8U * 1024U},
     169                 :            :         {512U, 4U * 1024U},
     170                 :            :         {1U * 1024U, 2U * 1024U},
     171                 :            :         {2U * 1024U, 1U * 1024U},
     172                 :            :         {4U * 1024U, 512U},
     173                 :            :         {8U * 1024U, 256U},
     174                 :            :         {16U * 1024U, 128U},
     175                 :            :         {32U * 1024U, 64U},
     176                 :            :         {64U * 1024U, 32U},
     177                 :            :         {128U * 1024U, 16U},
     178                 :            :         {256U * 1024U, 8U},
     179                 :            :         {512U * 1024U, 4U},
     180                 :            :         {1U * 1024U * 1024U, 4U},
     181                 :            :         {4U * 1024U * 1024U, 2U},
     182                 :            :         {8U * 1024U * 1024U, 0U},
     183                 :            :         {16U * 1024U * 1024U, 0U},
     184                 :            :     },
     185                 :            :     // L5 ~ 92 MiB.
     186                 :            :     {
     187                 :            :         {32U, 128U * 1024U},
     188                 :            :         {64U, 64U * 1024U},
     189                 :            :         {128U, 32U * 1024U},
     190                 :            :         {256U, 16U * 1024U},
     191                 :            :         {512U, 8U * 1024U},
     192                 :            :         {1U * 1024U, 4U * 1024U},
     193                 :            :         {2U * 1024U, 2U * 1024U},
     194                 :            :         {4U * 1024U, 1U * 1024U},
     195                 :            :         {8U * 1024U, 512U},
     196                 :            :         {16U * 1024U, 256U},
     197                 :            :         {32U * 1024U, 128U},
     198                 :            :         {64U * 1024U, 64U},
     199                 :            :         {128U * 1024U, 32U},
     200                 :            :         {256U * 1024U, 16U},
     201                 :            :         {512U * 1024U, 8U},
     202                 :            :         {1U * 1024U * 1024U, 8U},
     203                 :            :         {4U * 1024U * 1024U, 4U},
     204                 :            :         {8U * 1024U * 1024U, 1U},
     205                 :            :         {16U * 1024U * 1024U, 0U},
     206                 :            :     },
     207                 :            :     // L6 ~ 200 MiB.
     208                 :            :     {
     209                 :            :         {32U, 256U * 1024U},
     210                 :            :         {64U, 128U * 1024U},
     211                 :            :         {128U, 64U * 1024U},
     212                 :            :         {256U, 32U * 1024U},
     213                 :            :         {512U, 16U * 1024U},
     214                 :            :         {1U * 1024U, 8U * 1024U},
     215                 :            :         {2U * 1024U, 4U * 1024U},
     216                 :            :         {4U * 1024U, 2U * 1024U},
     217                 :            :         {8U * 1024U, 1U * 1024U},
     218                 :            :         {16U * 1024U, 512U},
     219                 :            :         {32U * 1024U, 256U},
     220                 :            :         {64U * 1024U, 128U},
     221                 :            :         {128U * 1024U, 64U},
     222                 :            :         {256U * 1024U, 32U},
     223                 :            :         {512U * 1024U, 16U},
     224                 :            :         {1U * 1024U * 1024U, 16U},
     225                 :            :         {4U * 1024U * 1024U, 8U},
     226                 :            :         {8U * 1024U * 1024U, 2U},
     227                 :            :         {16U * 1024U * 1024U, 1U},
     228                 :            :     },
     229                 :            :     // L7 ~ 264 MiB.
     230                 :            :     {
     231                 :            :         {32U, 256U * 1024U},
     232                 :            :         {64U, 128U * 1024U},
     233                 :            :         {128U, 64U * 1024U},
     234                 :            :         {256U, 32U * 1024U},
     235                 :            :         {512U, 16U * 1024U},
     236                 :            :         {1U * 1024U, 8U * 1024U},
     237                 :            :         {2U * 1024U, 4U * 1024U},
     238                 :            :         {4U * 1024U, 2U * 1024U},
     239                 :            :         {8U * 1024U, 1U * 1024U},
     240                 :            :         {16U * 1024U, 512U},
     241                 :            :         {32U * 1024U, 256U},
     242                 :            :         {64U * 1024U, 128U},
     243                 :            :         {128U * 1024U, 64U},
     244                 :            :         {256U * 1024U, 32U},
     245                 :            :         {512U * 1024U, 16U},
     246                 :            :         {1U * 1024U * 1024U, 16U},
     247                 :            :         {4U * 1024U * 1024U, 16U},
     248                 :            :         {8U * 1024U * 1024U, 4U},
     249                 :            :         {16U * 1024U * 1024U, 2U},
     250                 :            :     },
     251                 :            :     // L8 ~ 528 MiB.
     252                 :            :     {
     253                 :            :         {32U, 512U * 1024U},
     254                 :            :         {64U, 256U * 1024U},
     255                 :            :         {128U, 128U * 1024U},
     256                 :            :         {256U, 64U * 1024U},
     257                 :            :         {512U, 32U * 1024U},
     258                 :            :         {1U * 1024U, 16U * 1024U},
     259                 :            :         {2U * 1024U, 8U * 1024U},
     260                 :            :         {4U * 1024U, 4U * 1024U},
     261                 :            :         {8U * 1024U, 2U * 1024U},
     262                 :            :         {16U * 1024U, 1U * 1024U},
     263                 :            :         {32U * 1024U, 512U},
     264                 :            :         {64U * 1024U, 256U},
     265                 :            :         {128U * 1024U, 128U},
     266                 :            :         {256U * 1024U, 64U},
     267                 :            :         {512U * 1024U, 32U},
     268                 :            :         {1U * 1024U * 1024U, 32U},
     269                 :            :         {4U * 1024U * 1024U, 32U},
     270                 :            :         {8U * 1024U * 1024U, 8U},
     271                 :            :         {16U * 1024U * 1024U, 4U},
     272                 :            :     },
     273                 :            :     // L9 ~ 656 MiB.
     274                 :            :     {
     275                 :            :         {32U, 512U * 1024U},
     276                 :            :         {64U, 256U * 1024U},
     277                 :            :         {128U, 128U * 1024U},
     278                 :            :         {256U, 64U * 1024U},
     279                 :            :         {512U, 32U * 1024U},
     280                 :            :         {1U * 1024U, 16U * 1024U},
     281                 :            :         {2U * 1024U, 8U * 1024U},
     282                 :            :         {4U * 1024U, 4U * 1024U},
     283                 :            :         {8U * 1024U, 2U * 1024U},
     284                 :            :         {16U * 1024U, 1U * 1024U},
     285                 :            :         {32U * 1024U, 512U},
     286                 :            :         {64U * 1024U, 256U},
     287                 :            :         {128U * 1024U, 128U},
     288                 :            :         {256U * 1024U, 64U},
     289                 :            :         {512U * 1024U, 32U},
     290                 :            :         {1U * 1024U * 1024U, 32U},
     291                 :            :         {4U * 1024U * 1024U, 32U},
     292                 :            :         {8U * 1024U * 1024U, 16U},
     293                 :            :         {16U * 1024U * 1024U, 8U},
     294                 :            :     }
     295                 :            : };
     296                 :            : // clang-format on
     297                 :            : 
     298                 :            : struct MemoryFreeNode final {
     299                 :            :   MemoryFreeNode* next{nullptr};
     300                 :            : };
     301                 :            : 
     302                 :            : struct MemoryChunk final {
     303                 :            :   void* ptr{nullptr};
     304                 :            :   size_t bytes{0};
     305                 :            : };
     306                 :            : 
     307                 :            : struct alignas(64) MemoryTierShard final {
     308                 :            :   SpinLock mtx;
     309                 :            :   std::atomic<MemoryFreeNode*> free_list_head{nullptr};
     310                 :            :   std::atomic<uint64_t> hit_count{0};
     311                 :            :   std::atomic<uint64_t> deallocate_count{0};
     312                 :            : };
     313                 :            : 
     314                 :            : struct MemoryChunkTally final {
     315                 :            :   MemoryChunk chunk;
     316                 :            :   size_t free_nodes{0};
     317                 :            : };
     318                 :            : 
     319                 :            : // NOLINTNEXTLINE(clang-analyzer-optin.performance.Padding)
     320                 :            : struct alignas(64) MemoryTierState final {
     321                 :            :   size_t max_size{0};
     322                 :            :   size_t block_size{0};
     323                 :            :   size_t blocks_per_chunk{0};
     324                 :            :   size_t next_chunk_blocks{0};
     325                 :            :   size_t initial_chunk_blocks{0};
     326                 :            :   size_t lazy_chunk_blocks{0};
     327                 :            :   size_t batch_size{kDefaultBatchSize};
     328                 :            : 
     329                 :            :   MemoryTierShard* shards{nullptr};
     330                 :            :   size_t shard_count{0};
     331                 :            :   std::vector<MemoryChunk> chunks;
     332                 :            :   std::mutex grow_mtx;
     333                 :            :   std::mutex clear_mtx;
     334                 :            :   std::atomic<bool> sharded{false};
     335                 :            :   std::atomic<uint32_t> contention_count{0U};
     336                 :            : 
     337                 :            :   std::atomic<uint64_t> chunk_count{0};
     338                 :            :   std::atomic<uint64_t> upstream_alloc_count{0};
     339                 :            :   std::atomic<uint64_t> upstream_alloc_bytes{0};
     340                 :            : };
     341                 :            : 
     342                 :            : struct alignas(64) MemoryOversizedCounters final {
     343                 :            :   std::atomic<uint64_t> alloc_count{0};
     344                 :            :   std::atomic<uint64_t> alloc_bytes{0};
     345                 :            :   std::atomic<uint64_t> dealloc_count{0};
     346                 :            : };
     347                 :            : 
     348   [ +  #  +  # ]:     716399 : static constexpr bool is_power_of_two(size_t x) noexcept { return x != 0 && ((x & (x - 1U)) == 0U); }
     349                 :            : 
     350                 :       3944 : static constexpr size_t round_up(size_t value, size_t alignment) noexcept {
     351                 :       3944 :   return (value + alignment - 1U) & ~(alignment - 1U);
     352                 :            : }
     353                 :            : 
     354                 :            : static constexpr bool default_tier_table_well_formed() noexcept {
     355                 :            :   // NOLINTNEXTLINE(modernize-loop-convert)
     356                 :            :   for (size_t level = 0; level < kMaxLevelCount; ++level) {
     357                 :            :     size_t prev_max_size = 0U;
     358                 :            : 
     359                 :            :     for (size_t t = 0; t < kMaxTierCount; ++t) {
     360                 :            :       const size_t max_size = kDefaultTierTable[level][t].max_size;
     361                 :            : 
     362                 :            :       if (max_size == 0U) {
     363                 :            :         break;
     364                 :            :       }
     365                 :            : 
     366                 :            :       if (max_size < sizeof(MemoryFreeNode)) {
     367                 :            :         return false;
     368                 :            :       }
     369                 :            : 
     370                 :            :       if (t > 0U && max_size <= prev_max_size) {
     371                 :            :         return false;
     372                 :            :       }
     373                 :            : 
     374                 :            :       prev_max_size = max_size;
     375                 :            :     }
     376                 :            :   }
     377                 :            : 
     378                 :            :   return true;
     379                 :            : }
     380                 :            : 
     381                 :            : static_assert(default_tier_table_well_formed(),
     382                 :            :               "MemoryPool: kDefaultTierTable contains a malformed row "
     383                 :            :               "(undersized tier or non-monotonic max_size)");
     384                 :            : 
     385                 :            : static std::atomic<size_t> next_thread_slot{0U};
     386                 :            : static thread_local size_t current_thread_slot_plus_one = 0U;
     387                 :            : 
     388                 :     147949 : static size_t current_thread_slot() noexcept {
     389         [ +  + ]:     147949 :   if VUNLIKELY (current_thread_slot_plus_one == 0U) {
     390                 :        135 :     current_thread_slot_plus_one = next_thread_slot.fetch_add(1U, std::memory_order_relaxed) + 1U;
     391                 :            :   }
     392                 :            : 
     393                 :     147949 :   return current_thread_slot_plus_one - 1U;
     394                 :            : }
     395                 :            : 
     396                 :     147523 : static size_t current_tier_shard(const MemoryTierState& state) noexcept {
     397                 :     147523 :   return current_thread_slot() & (state.shard_count - 1U);
     398                 :            : }
     399                 :            : 
     400                 :        281 : static size_t default_tier_shard_count() noexcept {
     401                 :        281 :   const size_t threads = std::thread::hardware_concurrency();
     402                 :        281 :   size_t count = kMinTierShardCount;
     403                 :            : 
     404   [ -  +  -  - ]:        281 :   while (count < threads && count < kMaxTierShardCount) {
     405                 :          0 :     count *= 2U;
     406                 :            :   }
     407                 :            : 
     408                 :        281 :   return count;
     409                 :            : }
     410                 :            : 
     411                 :     742875 : static MemoryFreeNode* head_of(const MemoryTierShard& shard) noexcept {
     412                 :     742875 :   return shard.free_list_head.load(std::memory_order_relaxed);
     413                 :            : }
     414                 :            : 
     415                 :     754171 : static void set_head(MemoryTierShard& shard, MemoryFreeNode* head) noexcept {
     416                 :     754171 :   shard.free_list_head.store(head, std::memory_order_relaxed);
     417                 :     756400 : }
     418                 :            : 
     419                 :     718464 : static void bump_counter(std::atomic<uint64_t>& counter) noexcept {
     420                 :    1437025 :   counter.store(counter.load(std::memory_order_relaxed) + 1U, std::memory_order_relaxed);
     421                 :     718645 : }
     422                 :            : 
     423                 :      76776 : static MemoryFreeNode* pop_free_node(MemoryTierShard& shard) noexcept {
     424                 :      76776 :   SpinLockGuard lock(shard.mtx);
     425                 :      77746 :   MemoryFreeNode* node = head_of(shard);
     426                 :            : 
     427         [ +  + ]:      77297 :   if (node == nullptr) {
     428                 :       6188 :     return nullptr;
     429                 :            :   }
     430                 :            : 
     431                 :      71109 :   set_head(shard, node->next);
     432                 :      71656 :   bump_counter(shard.hit_count);
     433                 :            : 
     434                 :      71939 :   return node;
     435                 :      78127 : }
     436                 :            : 
     437                 :       4110 : static MemoryFreeNode* steal_free_nodes(MemoryTierState& state, size_t target_index) noexcept {
     438                 :       4110 :   MemoryTierShard& target = state.shards[target_index];
     439                 :            : 
     440         [ +  + ]:      14458 :   for (size_t offset = 1U; offset < state.shard_count; ++offset) {
     441                 :      13786 :     MemoryTierShard& source = state.shards[(target_index + offset) & (state.shard_count - 1U)];
     442                 :      13786 :     MemoryFreeNode* first = nullptr;
     443                 :      13786 :     MemoryFreeNode* last = nullptr;
     444                 :            : 
     445         [ +  + ]:      13786 :     if (head_of(source) == nullptr) {
     446                 :      10249 :       continue;
     447                 :            :     }
     448                 :            : 
     449                 :            :     {
     450                 :       3544 :       SpinLockGuard source_lock(source.mtx);
     451                 :       3552 :       first = head_of(source);
     452                 :            : 
     453         [ +  + ]:       3549 :       if (first == nullptr) {
     454                 :         99 :         continue;
     455                 :            :       }
     456                 :            : 
     457                 :       3450 :       last = first;
     458                 :            : 
     459                 :       3450 :       size_t count = 1U;
     460                 :            : 
     461   [ +  +  +  + ]:      39341 :       while (count < state.batch_size && last->next != nullptr) {
     462                 :      35891 :         last = last->next;
     463                 :      35891 :         ++count;
     464                 :            :       }
     465                 :            : 
     466                 :       3450 :       set_head(source, last->next);
     467                 :       3450 :       last->next = nullptr;
     468                 :       3450 :       bump_counter(source.hit_count);
     469         [ +  + ]:       3552 :     }
     470                 :            : 
     471                 :       3453 :     MemoryFreeNode* cached = first->next;
     472                 :       3453 :     first->next = nullptr;
     473                 :            : 
     474         [ +  + ]:       3453 :     if (cached != nullptr) {
     475                 :       3008 :       SpinLockGuard target_lock(target.mtx);
     476                 :       3008 :       last->next = head_of(target);
     477                 :       3007 :       set_head(target, cached);
     478                 :       3008 :     }
     479                 :            : 
     480                 :       3453 :     return first;
     481                 :            :   }
     482                 :            : 
     483                 :        672 :   return nullptr;
     484                 :            : }
     485                 :            : 
     486                 :      76814 : static MemoryFreeNode* try_allocate_from_shards(MemoryTierState& state, size_t shard_index) noexcept {
     487                 :      76814 :   MemoryFreeNode* node = pop_free_node(state.shards[shard_index]);
     488                 :            : 
     489         [ +  + ]:      77574 :   if VLIKELY (node != nullptr) {
     490                 :      71555 :     return node;
     491                 :            :   }
     492                 :            : 
     493         [ +  + ]:       6019 :   if (!state.sharded.load(std::memory_order_relaxed)) {
     494                 :       2075 :     return nullptr;
     495                 :            :   }
     496                 :            : 
     497                 :       4112 :   return steal_free_nodes(state, shard_index);
     498                 :            : }
     499                 :            : 
     500                 :            : // state.grow_mtx must be held.  When allocated is non-null, one node is removed from the new chunk.
     501                 :       1237 : static bool grow_tier_chunk(MemoryTierState& state, size_t shard_index, MemoryFreeNode** allocated) noexcept {
     502                 :       1237 :   size_t blocks = state.next_chunk_blocks;
     503                 :            : 
     504         [ -  + ]:       1237 :   if VUNLIKELY (blocks > state.blocks_per_chunk) {
     505                 :            :     blocks = state.blocks_per_chunk;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     506                 :            :   }
     507                 :            : 
     508   [ +  +  +  + ]:       1237 :   if (allocated != nullptr && blocks > state.lazy_chunk_blocks) {
     509                 :        193 :     blocks = state.lazy_chunk_blocks;
     510                 :            :   }
     511                 :            : 
     512                 :       1237 :   const size_t block_size = state.block_size;
     513                 :       1237 :   const size_t chunk_bytes = block_size * blocks;
     514                 :            : 
     515         [ -  + ]:       1237 :   if VUNLIKELY (chunk_bytes / block_size != blocks) {
     516                 :            :     return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     517                 :            :   }
     518                 :            : 
     519                 :       1237 :   void* ptr = ::operator new(chunk_bytes, std::align_val_t{MemoryPool::kBlockAlignment}, std::nothrow);
     520                 :            : 
     521         [ -  + ]:       1237 :   if VUNLIKELY (ptr == nullptr) {
     522                 :            :     return false;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     523                 :            :   }
     524                 :            : 
     525                 :       1237 :   auto* base = static_cast<std::byte*>(ptr);
     526                 :       1237 :   auto* local_tail = ::new (base + (blocks - 1U) * block_size) MemoryFreeNode{nullptr};
     527                 :       1237 :   MemoryFreeNode* local_head = local_tail;
     528                 :            : 
     529         [ +  + ]:     773334 :   for (size_t i = blocks - 1U; i > 0; --i) {
     530                 :     772097 :     local_head = ::new (base + (i - 1U) * block_size) MemoryFreeNode{local_head};
     531                 :            :   }
     532                 :            : 
     533                 :            :   try {
     534         [ +  - ]:       1237 :     state.chunks.push_back(MemoryChunk{ptr, chunk_bytes});
     535         [ -  - ]:          0 :   } catch (std::exception&) {
     536                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     537                 :            :     ::operator delete(ptr, chunk_bytes, std::align_val_t{MemoryPool::kBlockAlignment});
     538                 :            :     return false;
     539                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     540                 :          0 :   }
     541                 :            : 
     542                 :       1237 :   state.upstream_alloc_count.fetch_add(1, std::memory_order_relaxed);
     543                 :       1237 :   state.upstream_alloc_bytes.fetch_add(chunk_bytes, std::memory_order_relaxed);
     544                 :       1237 :   state.chunk_count.fetch_add(1, std::memory_order_relaxed);
     545                 :            : 
     546                 :            :   {
     547                 :       1237 :     MemoryTierShard& shard = state.shards[shard_index];
     548                 :       1237 :     SpinLockGuard lock(shard.mtx);
     549                 :            : 
     550                 :       1237 :     local_tail->next = head_of(shard);
     551                 :            : 
     552         [ +  + ]:       1237 :     if (allocated != nullptr) {
     553                 :       1234 :       *allocated = local_head;
     554                 :       1234 :       local_head = local_head->next;
     555                 :       1234 :       (*allocated)->next = nullptr;
     556                 :       1234 :       bump_counter(shard.hit_count);
     557                 :            :     }
     558                 :            : 
     559                 :       1237 :     set_head(shard, local_head);
     560                 :       1237 :   }
     561                 :            : 
     562                 :       1237 :   const size_t doubled = blocks * 2U;
     563         [ +  + ]:       1237 :   const size_t target = (doubled < blocks || doubled > state.blocks_per_chunk)
     564         [ +  - ]:       2474 :                             ? state.blocks_per_chunk
     565                 :            :                             : doubled;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     566                 :            : 
     567         [ +  + ]:       1237 :   if (target > state.next_chunk_blocks) {
     568                 :        809 :     state.next_chunk_blocks = target;
     569                 :            :   }
     570                 :            : 
     571                 :       1237 :   return true;
     572                 :            : }
     573                 :            : 
     574                 :     358565 : static void* tier_allocate(MemoryTierState& state) noexcept {
     575                 :     358565 :   size_t shard_index = 0U;
     576                 :     358565 :   bool sharded = state.sharded.load(std::memory_order_relaxed);
     577                 :            : 
     578         [ +  + ]:     358769 :   if (!sharded) {
     579                 :     284502 :     MemoryTierShard& primary = state.shards[0];
     580                 :            : 
     581         [ +  + ]:     284502 :     if (primary.mtx.try_lock()) {
     582                 :     284441 :       MemoryFreeNode* node = head_of(primary);
     583                 :            : 
     584         [ +  + ]:     284439 :       if VLIKELY (node != nullptr) {
     585                 :     283400 :         set_head(primary, node->next);
     586                 :     283403 :         bump_counter(primary.hit_count);
     587                 :            :       }
     588                 :            : 
     589                 :     284444 :       primary.mtx.unlock();
     590                 :            : 
     591         [ +  + ]:     284442 :       if VLIKELY (node != nullptr) {
     592                 :     283402 :         return node;
     593                 :            :       }
     594                 :            :     } else {
     595                 :         61 :       const uint32_t contentions = state.contention_count.fetch_add(1U, std::memory_order_relaxed) + 1U;
     596                 :            : 
     597         [ +  + ]:         61 :       if (contentions >= kShardingContentionThreshold) {
     598                 :          6 :         state.sharded.store(true, std::memory_order_relaxed);
     599                 :          6 :         sharded = true;
     600                 :            :       }
     601                 :            :     }
     602                 :            :   }
     603                 :            : 
     604         [ +  + ]:      75368 :   if (sharded) {
     605                 :      74188 :     shard_index = current_tier_shard(state);
     606                 :            :   }
     607                 :            : 
     608                 :      75515 :   MemoryFreeNode* node = try_allocate_from_shards(state, shard_index);
     609                 :            : 
     610         [ +  + ]:      76056 :   if VLIKELY (node != nullptr) {
     611                 :      74553 :     return node;
     612                 :            :   }
     613                 :            : 
     614                 :            :   {
     615                 :       1503 :     std::lock_guard grow_lock(state.grow_mtx);
     616                 :            : 
     617                 :       1507 :     node = try_allocate_from_shards(state, shard_index);
     618                 :            : 
     619         [ +  + ]:       1507 :     if VLIKELY (node != nullptr) {
     620                 :        273 :       return node;
     621                 :            :     }
     622                 :            : 
     623         [ +  - ]:       1234 :     if VLIKELY (grow_tier_chunk(state, shard_index, &node)) {
     624                 :       1234 :       return node;
     625                 :            :     }
     626         [ -  + ]:       1507 :   }
     627                 :            : 
     628                 :          0 :   std::lock_guard clear_lock(state.clear_mtx);
     629                 :            : 
     630                 :          0 :   node = try_allocate_from_shards(state, shard_index);
     631                 :            : 
     632         [ #  # ]:          0 :   if (node != nullptr) {
     633                 :          0 :     return node;
     634                 :            :   }
     635                 :            : 
     636                 :          0 :   std::lock_guard grow_lock(state.grow_mtx);
     637                 :            : 
     638         [ #  # ]:          0 :   return grow_tier_chunk(state, shard_index, &node) ? node : nullptr;
     639                 :          0 : }
     640                 :            : 
     641                 :     358752 : static void tier_deallocate(MemoryTierState& state, void* p) noexcept {
     642         [ +  + ]:     358752 :   if (!state.sharded.load(std::memory_order_relaxed)) {
     643                 :     282948 :     MemoryTierShard& primary = state.shards[0];
     644                 :            : 
     645         [ +  + ]:     282948 :     if (primary.mtx.try_lock()) {
     646                 :     282879 :       set_head(primary, ::new (p) MemoryFreeNode{head_of(primary)});
     647                 :     282881 :       bump_counter(primary.deallocate_count);
     648                 :     282883 :       primary.mtx.unlock();
     649                 :            : 
     650                 :     282945 :       return;
     651                 :            :     }
     652                 :            : 
     653                 :         69 :     const uint32_t contentions = state.contention_count.fetch_add(1U, std::memory_order_relaxed) + 1U;
     654                 :            : 
     655         [ +  + ]:         69 :     if (contentions < kShardingContentionThreshold) {
     656                 :         63 :       SpinLockGuard lock(primary.mtx);
     657                 :         63 :       set_head(primary, ::new (p) MemoryFreeNode{head_of(primary)});
     658                 :         63 :       bump_counter(primary.deallocate_count);
     659                 :            : 
     660                 :         63 :       return;
     661                 :         63 :     }
     662                 :            : 
     663                 :          6 :     state.sharded.store(true, std::memory_order_relaxed);
     664                 :            :   }
     665                 :            : 
     666                 :      76043 :   MemoryTierShard& shard = state.shards[current_tier_shard(state)];
     667                 :      75991 :   SpinLockGuard lock(shard.mtx);
     668                 :            : 
     669                 :      76786 :   set_head(shard, ::new (p) MemoryFreeNode{head_of(shard)});
     670                 :      76813 :   bump_counter(shard.deallocate_count);
     671                 :      77000 : }
     672                 :            : 
     673                 :          3 : static void prealloc_full_quota(MemoryTierState& state) noexcept {
     674                 :          3 :   std::lock_guard grow_lock(state.grow_mtx);
     675                 :            : 
     676                 :          3 :   state.next_chunk_blocks = state.blocks_per_chunk;
     677                 :          3 :   const bool ok = grow_tier_chunk(state, 0U, nullptr);
     678                 :            : 
     679         [ -  + ]:          3 :   if VUNLIKELY (!ok) {
     680                 :            :     state.next_chunk_blocks = state.initial_chunk_blocks;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     681                 :            :   }
     682                 :            : 
     683         [ -  + ]:          3 :   if VUNLIKELY (!ok) {
     684                 :            :     // LCOV_EXCL_START GCOVR_EXCL_START
     685                 :            :     MEMORY_POOL_LOG(
     686                 :            :         "MemoryPool: prealloc failed for tier (max_size=%zu, blocks_per_chunk=%zu); "
     687                 :            :         "tier reverts to lazy growth.",
     688                 :            :         state.max_size, state.blocks_per_chunk);
     689                 :            :     // LCOV_EXCL_STOP GCOVR_EXCL_STOP
     690                 :            :   }
     691                 :          3 : }
     692                 :            : 
     693                 :        275 : static bool validate_tiers_log(const std::vector<MemoryPool::Tier>& tiers) noexcept {
     694                 :            :   static constexpr size_t kMaxTierSize = SIZE_MAX - MemoryPool::kBlockAlignment + 1U;
     695                 :            : 
     696         [ +  + ]:        275 :   if VUNLIKELY (tiers.size() > kMaxTierCount) {
     697                 :          1 :     MEMORY_POOL_LOG("MemoryPool: tier count %zu exceeds max %zu; falling back to default pyramid.", tiers.size(),
     698                 :            :                     kMaxTierCount);
     699                 :            : 
     700                 :          1 :     return false;
     701                 :            :   }
     702                 :            : 
     703         [ +  + ]:       4871 :   for (size_t i = 0; i < tiers.size(); ++i) {
     704         [ +  + ]:       4602 :     if VUNLIKELY (tiers[i].max_size == 0) {
     705                 :          1 :       MEMORY_POOL_LOG("MemoryPool: tier %zu has max_size == 0; falling back to default pyramid.", i);
     706                 :            : 
     707                 :          1 :       return false;
     708                 :            :     }
     709                 :            : 
     710         [ +  + ]:       4601 :     if VUNLIKELY (tiers[i].max_size < sizeof(MemoryFreeNode)) {
     711                 :          1 :       MEMORY_POOL_LOG(
     712                 :            :           "MemoryPool: tier %zu max_size (%zu) is below the minimum block size %zu; "
     713                 :            :           "falling back to default pyramid.",
     714                 :            :           i, tiers[i].max_size, sizeof(MemoryFreeNode));
     715                 :            : 
     716                 :          1 :       return false;
     717                 :            :     }
     718                 :            : 
     719         [ +  + ]:       4600 :     if VUNLIKELY (tiers[i].max_size > kMaxTierSize) {
     720                 :          1 :       MEMORY_POOL_LOG("MemoryPool: tier %zu max_size overflows after alignment rounding; falling back.", i);
     721                 :            : 
     722                 :          1 :       return false;
     723                 :            :     }
     724                 :            : 
     725   [ +  +  +  +  :       4599 :     if VUNLIKELY (i > 0 && tiers[i].max_size <= tiers[i - 1].max_size) {
                   +  + ]
     726                 :          2 :       MEMORY_POOL_LOG("MemoryPool: tier %zu max_size is not strictly increasing; falling back to default pyramid.", i);
     727                 :            : 
     728                 :          2 :       return false;
     729                 :            :     }
     730                 :            :   }
     731                 :            : 
     732                 :        269 :   return true;
     733                 :            : }
     734                 :            : 
     735                 :        252 : static MemoryPool::Config create_memory_config(int level, bool prealloc) {
     736   [ +  -  +  +  :        252 :   if VUNLIKELY (level < kMinMemoryLevel || level > kMaxMemoryLevel) {
                   +  + ]
     737                 :          1 :     MEMORY_POOL_LOG("MemoryPool: level %d out of range [%d, %d], clamped.", level, kMinMemoryLevel, kMaxMemoryLevel);
     738                 :            : 
     739         [ -  + ]:          1 :     level = (level < kMinMemoryLevel) ? kMinMemoryLevel : kMaxMemoryLevel;
     740                 :            :   }
     741                 :            : 
     742                 :        252 :   const auto row_index = static_cast<size_t>(level - kMinMemoryLevel);
     743                 :        252 :   const auto& row = kDefaultTierTable[row_index];
     744                 :            : 
     745                 :        252 :   MemoryPool::Config config;
     746                 :        252 :   config.prealloc = prealloc;
     747         [ +  - ]:        252 :   config.tiers.reserve(kMaxTierCount);
     748                 :            : 
     749   [ +  -  +  + ]:       5040 :   for (size_t i = 0; i < kMaxTierCount && row[i].max_size != 0; ++i) {
     750         [ +  - ]:       4788 :     config.tiers.emplace_back(row[i]);
     751                 :            :   }
     752                 :            : 
     753                 :        252 :   return config;
     754                 :            : }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     755                 :            : 
     756                 :            : struct MemoryPool::Impl final {  // NOLINT(clang-analyzer-optin.performance.Padding)
     757                 :            :   alignas(64) size_t dispatch_max_sizes[kMaxTierCount]{};
     758                 :            :   MemoryTierState* dispatch_states[kMaxTierCount]{};
     759                 :            :   size_t dispatch_count{0};
     760                 :            : 
     761                 :            :   MemoryTierState* tier_states[kMaxTierCount]{};
     762                 :            :   size_t tier_count{0};
     763                 :            :   std::unique_ptr<MemoryTierShard[]> shards;
     764                 :            :   std::vector<std::unique_ptr<MemoryTierState>> owned_states;
     765                 :            : 
     766                 :            :   std::unique_ptr<MemoryOversizedCounters[]> oversized;
     767                 :            :   size_t oversized_count{0};
     768                 :            : };
     769                 :            : 
     770         [ +  - ]:          4 : MemoryPool::MemoryPool() : MemoryPool(Config{}) {}
     771                 :            : 
     772         [ +  - ]:         16 : MemoryPool::MemoryPool(int level, bool prealloc) : MemoryPool(create_memory_config(level, prealloc)) {}
     773                 :            : 
     774                 :        281 : MemoryPool::MemoryPool(const Config& config) : impl_(std::make_unique<Impl>()) {
     775                 :        281 :   const size_t shard_count = default_tier_shard_count();
     776                 :            : 
     777         [ +  - ]:        281 :   impl_->oversized = std::make_unique<MemoryOversizedCounters[]>(shard_count);
     778                 :        281 :   impl_->oversized_count = shard_count;
     779                 :            : 
     780         [ +  + ]:        281 :   if (config.tiers.empty()) {
     781                 :          6 :     impl_->tier_count = 0;
     782                 :          6 :     return;
     783                 :            :   }
     784                 :            : 
     785                 :        275 :   std::vector<Tier> fallback;
     786                 :        275 :   const bool use_caller = validate_tiers_log(config.tiers);
     787                 :            : 
     788         [ +  + ]:        275 :   if VUNLIKELY (!use_caller) {
     789                 :          6 :     const auto& row = kDefaultTierTable[kDefaultMemoryLevel - kMinMemoryLevel];
     790         [ +  - ]:          6 :     fallback.assign(row, row + kMaxTierCount);
     791                 :            :   }
     792                 :            : 
     793         [ +  + ]:        275 :   const std::vector<Tier>& source = use_caller ? config.tiers : fallback;
     794         [ +  + ]:        275 :   const size_t batch_size = config.batch_size == 0U ? kDefaultBatchSize : config.batch_size;
     795                 :            : 
     796         [ +  + ]:        275 :   if VUNLIKELY (config.batch_size == 0U) {
     797   [ +  -  +  - ]:          1 :     MEMORY_POOL_LOG("MemoryPool: batch_size is 0; fallback to %zu.", kDefaultBatchSize);
     798                 :            :   }
     799                 :            : 
     800         [ +  - ]:        275 :   impl_->owned_states.reserve(source.size());
     801                 :            : 
     802                 :        275 :   size_t managed = 0;
     803                 :            : 
     804         [ +  + ]:       4990 :   for (const auto& cfg : source) {
     805   [ +  +  +  + ]:       4715 :     if (cfg.max_size != 0U && cfg.blocks_per_chunk != 0U) {
     806                 :       3944 :       ++managed;
     807                 :            :     }
     808                 :            :   }
     809                 :            : 
     810         [ +  - ]:        275 :   impl_->shards = std::make_unique<MemoryTierShard[]>(managed * shard_count);
     811                 :            : 
     812                 :        275 :   size_t live = 0;
     813                 :        275 :   size_t dispatch = 0;
     814                 :            : 
     815         [ +  + ]:       4990 :   for (const auto& cfg : source) {
     816         [ +  + ]:       4715 :     if VUNLIKELY (cfg.max_size == 0U) {
     817                 :        771 :       continue;
     818                 :            :     }
     819                 :            : 
     820                 :       4709 :     impl_->dispatch_max_sizes[dispatch] = cfg.max_size;
     821                 :            : 
     822         [ +  + ]:       4709 :     if VUNLIKELY (cfg.blocks_per_chunk == 0U) {
     823                 :        765 :       impl_->dispatch_states[dispatch] = nullptr;
     824                 :        765 :       ++dispatch;
     825                 :        765 :       continue;
     826                 :            :     }
     827                 :            : 
     828         [ +  - ]:       3944 :     auto state = std::make_unique<MemoryTierState>();
     829                 :       3944 :     state->max_size = cfg.max_size;
     830                 :       3944 :     state->blocks_per_chunk = cfg.blocks_per_chunk;
     831                 :       3944 :     state->batch_size = batch_size;
     832                 :       3944 :     state->shards = impl_->shards.get() + live * shard_count;
     833                 :       3944 :     state->shard_count = shard_count;
     834         [ +  - ]:       3944 :     state->chunks.reserve(kInitialChunksReserve);
     835                 :       3944 :     state->block_size = round_up(cfg.max_size, kBlockAlignment);
     836                 :            : 
     837         [ +  - ]:       3944 :     size_t initial = (state->block_size > 0U) ? (kInitialChunkBytesTarget / state->block_size) : kInitialBlocksPerChunk;
     838                 :            : 
     839         [ +  + ]:       3944 :     if (initial < kInitialBlocksPerChunk) {
     840                 :        975 :       initial = kInitialBlocksPerChunk;
     841                 :            :     }
     842                 :            : 
     843         [ +  + ]:       3944 :     if (initial > state->blocks_per_chunk) {
     844                 :         49 :       initial = state->blocks_per_chunk;
     845                 :            :     }
     846                 :            : 
     847                 :       3944 :     state->initial_chunk_blocks = initial;
     848                 :       3944 :     state->next_chunk_blocks = initial;
     849                 :            : 
     850         [ +  + ]:       3963 :     const size_t lazy_blocks = config.lazy_scale ? std::max(state->blocks_per_chunk / kLazyChunkQuotaDivisor,
     851                 :         19 :                                                             kMinLazyChunkBytes / state->block_size)
     852                 :       3925 :                                                  : kMaxLazyChunkBytes / state->block_size;
     853                 :            : 
     854                 :       3944 :     state->lazy_chunk_blocks = std::clamp(lazy_blocks, size_t{1}, state->blocks_per_chunk);
     855                 :            : 
     856                 :       3944 :     impl_->tier_states[live] = state.get();
     857                 :       3944 :     impl_->dispatch_states[dispatch] = state.get();
     858         [ +  - ]:       3944 :     impl_->owned_states.emplace_back(std::move(state));
     859                 :            : 
     860                 :       3944 :     ++live;
     861                 :       3944 :     ++dispatch;
     862                 :       3944 :   }
     863                 :            : 
     864         [ +  + ]:        275 :   impl_->dispatch_count = (live == 0U) ? 0U : dispatch;
     865                 :        275 :   impl_->tier_count = live;
     866                 :            : 
     867         [ +  + ]:        275 :   if (config.prealloc) {
     868         [ +  + ]:          5 :     for (auto& state : impl_->owned_states) {
     869                 :          3 :       prealloc_full_quota(*state);
     870                 :            :     }
     871                 :            :   }
     872                 :        275 : }
     873                 :            : 
     874                 :        281 : MemoryPool::~MemoryPool() {
     875                 :            : #ifdef _WIN32
     876                 :            :   if (Utils::is_terminating()) {
     877                 :            :     (void)impl_.release();
     878                 :            :     return;
     879                 :            :   }
     880                 :            : #endif
     881                 :            : 
     882         [ +  + ]:       4225 :   for (auto& state : impl_->owned_states) {
     883         [ +  + ]:       4979 :     for (const MemoryChunk& chunk : state->chunks) {
     884                 :       1035 :       ::operator delete(chunk.ptr, chunk.bytes, std::align_val_t{kBlockAlignment});
     885                 :            :     }
     886                 :            : 
     887                 :       3944 :     state->chunks.clear();
     888                 :            : 
     889         [ +  + ]:      35496 :     for (size_t index = 0U; index < state->shard_count; ++index) {
     890                 :      31552 :       set_head(state->shards[index], nullptr);
     891                 :            :     }
     892                 :            :   }
     893                 :        281 : }
     894                 :            : 
     895                 :     359066 : void* MemoryPool::allocate(size_t bytes, size_t alignment) noexcept {
     896         [ +  + ]:     359066 :   if VUNLIKELY (!is_power_of_two(alignment)) {
     897                 :          1 :     MEMORY_POOL_LOG("MemoryPool::allocate: alignment %zu is not a power of two; returning nullptr.", alignment);
     898                 :            : 
     899                 :          1 :     return nullptr;
     900                 :            :   }
     901                 :            : 
     902                 :     359260 :   const size_t idx = find_tier(bytes);
     903                 :            : 
     904   [ +  #  +  +  :     359028 :   if VUNLIKELY (idx == kMaxTierCount || alignment > kBlockAlignment || impl_->dispatch_states[idx] == nullptr) {
          +  #  +  +  +  
                      + ]
     905         [ +  + ]:        101 :     void* p = (alignment > kBlockAlignment) ? ::operator new(bytes, std::align_val_t{alignment}, std::nothrow)
     906                 :         16 :                                             : ::operator new(bytes, std::nothrow);
     907                 :            : 
     908         [ -  + ]:        101 :     if VUNLIKELY (p == nullptr) {
     909                 :            :       return nullptr;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     910                 :            :     }
     911                 :            : 
     912                 :        101 :     MemoryOversizedCounters& counters = impl_->oversized[current_thread_slot() & (impl_->oversized_count - 1U)];
     913                 :        101 :     counters.alloc_count.fetch_add(1, std::memory_order_relaxed);
     914                 :        101 :     counters.alloc_bytes.fetch_add(bytes, std::memory_order_relaxed);
     915                 :            : 
     916                 :        101 :     return p;
     917                 :            :   }
     918                 :            : 
     919                 :     358363 :   return tier_allocate(*impl_->dispatch_states[idx]);
     920                 :            : }
     921                 :            : 
     922                 :     359204 : void MemoryPool::deallocate(void* p, size_t bytes, size_t alignment) noexcept {
     923         [ #  + ]:     359204 :   if VUNLIKELY (!is_power_of_two(alignment)) {
     924                 :          0 :     MEMORY_POOL_LOG("MemoryPool::deallocate: alignment %zu is not a power of two; leaking %p.", alignment, p);
     925                 :            : 
     926                 :          1 :     return;
     927                 :            :   }
     928                 :            : 
     929         [ +  + ]:     359463 :   if VUNLIKELY (p == nullptr) {
     930                 :          2 :     return;
     931                 :            :   }
     932                 :            : 
     933                 :     359461 :   const size_t idx = find_tier(bytes);
     934                 :            : 
     935   [ +  #  +  +  :     359286 :   if VUNLIKELY (idx == kMaxTierCount || alignment > kBlockAlignment || impl_->dispatch_states[idx] == nullptr) {
          +  #  +  +  +  
                      + ]
     936         [ +  + ]:        100 :     if (alignment > kBlockAlignment) {
     937                 :         84 :       ::operator delete(p, bytes, std::align_val_t{alignment});
     938                 :            :     } else {
     939                 :         16 :       ::operator delete(p, bytes);
     940                 :            :     }
     941                 :            : 
     942                 :        100 :     impl_->oversized[current_thread_slot() & (impl_->oversized_count - 1U)].dealloc_count.fetch_add(
     943                 :            :         1, std::memory_order_relaxed);
     944                 :            : 
     945                 :        100 :     return;
     946                 :            :   }
     947                 :            : 
     948                 :     358724 :   tier_deallocate(*impl_->dispatch_states[idx], p);
     949                 :            : }
     950                 :            : 
     951                 :         17 : size_t MemoryPool::get_tier_count() const noexcept { return impl_->tier_count; }
     952                 :            : 
     953                 :         46 : std::vector<MemoryPool::TierStats> MemoryPool::get_stats() const noexcept {
     954                 :         46 :   const size_t count = impl_->tier_count;
     955                 :            : 
     956                 :         46 :   std::vector<TierStats> result;
     957                 :            : 
     958                 :            :   try {
     959         [ +  - ]:         46 :     result.reserve(count);
     960                 :            :   } catch (...) {  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     961                 :            :     return {};     // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     962                 :            :   }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     963                 :            : 
     964         [ +  + ]:        348 :   for (size_t i = 0; i < count; ++i) {
     965                 :        302 :     const MemoryTierState& state = *impl_->tier_states[i];
     966                 :        302 :     uint64_t hits = 0U;
     967                 :        302 :     uint64_t deallocs = 0U;
     968                 :            : 
     969         [ +  + ]:       2718 :     for (size_t index = 0U; index < state.shard_count; ++index) {
     970                 :       2416 :       hits += state.shards[index].hit_count.load(std::memory_order_relaxed);
     971                 :       4832 :       deallocs += state.shards[index].deallocate_count.load(std::memory_order_relaxed);
     972                 :            :     }
     973                 :            : 
     974                 :        302 :     TierStats item;
     975                 :        302 :     item.max_size = state.max_size;
     976                 :        302 :     item.blocks_per_chunk = state.blocks_per_chunk;
     977                 :        302 :     item.block_size = state.block_size;
     978                 :        302 :     item.hit_count = hits;
     979                 :        302 :     item.deallocate_count = deallocs;
     980         [ +  - ]:        302 :     item.in_use_blocks = (hits >= deallocs) ? (hits - deallocs) : 0U;
     981                 :        302 :     item.upstream_alloc_count = state.upstream_alloc_count.load(std::memory_order_relaxed);
     982                 :        302 :     item.upstream_alloc_bytes = state.upstream_alloc_bytes.load(std::memory_order_relaxed);
     983                 :        302 :     item.chunk_count = state.chunk_count.load(std::memory_order_relaxed);
     984                 :            : 
     985                 :        302 :     result.emplace_back(item);
     986                 :            :   }
     987                 :            : 
     988                 :         46 :   return result;
     989                 :         46 : }
     990                 :            : 
     991                 :         34 : MemoryPool::OversizedStats MemoryPool::get_oversized_stats() const noexcept {
     992                 :         34 :   OversizedStats result;
     993                 :            : 
     994         [ +  + ]:        306 :   for (size_t i = 0; i < impl_->oversized_count; ++i) {
     995                 :        272 :     const MemoryOversizedCounters& counters = impl_->oversized[i];
     996                 :            : 
     997                 :        272 :     result.alloc_count += counters.alloc_count.load(std::memory_order_relaxed);
     998                 :        272 :     result.alloc_bytes += counters.alloc_bytes.load(std::memory_order_relaxed);
     999                 :        544 :     result.dealloc_count += counters.dealloc_count.load(std::memory_order_relaxed);
    1000                 :            :   }
    1001                 :            : 
    1002                 :         34 :   return result;
    1003                 :            : }
    1004                 :            : 
    1005                 :          1 : void MemoryPool::reset_stats() noexcept {
    1006                 :          1 :   const size_t count = impl_->tier_count;
    1007                 :            : 
    1008         [ +  + ]:          2 :   for (size_t i = 0; i < count; ++i) {
    1009                 :          1 :     MemoryTierState& state = *impl_->tier_states[i];
    1010                 :            : 
    1011         [ +  + ]:          9 :     for (size_t index = 0U; index < state.shard_count; ++index) {
    1012                 :          8 :       MemoryTierShard& shard = state.shards[index];
    1013                 :          8 :       SpinLockGuard lock(shard.mtx);
    1014                 :            : 
    1015                 :          8 :       shard.hit_count.store(0, std::memory_order_relaxed);
    1016                 :          8 :       shard.deallocate_count.store(0, std::memory_order_relaxed);
    1017                 :          8 :     }
    1018                 :            :   }
    1019                 :            : 
    1020         [ +  + ]:          9 :   for (size_t i = 0; i < impl_->oversized_count; ++i) {
    1021                 :          8 :     MemoryOversizedCounters& counters = impl_->oversized[i];
    1022                 :            : 
    1023                 :          8 :     counters.alloc_count.store(0, std::memory_order_relaxed);
    1024                 :          8 :     counters.alloc_bytes.store(0, std::memory_order_relaxed);
    1025                 :          8 :     counters.dealloc_count.store(0, std::memory_order_relaxed);
    1026                 :            :   }
    1027                 :          1 : }
    1028                 :            : 
    1029                 :       2688 : static MemoryFreeNode* detach_free_list(MemoryTierShard& shard) noexcept {
    1030                 :       2688 :   SpinLockGuard lock(shard.mtx);
    1031                 :            : 
    1032                 :       2688 :   MemoryFreeNode* head = head_of(shard);
    1033                 :       2688 :   set_head(shard, nullptr);
    1034                 :            : 
    1035                 :       5376 :   return head;
    1036                 :       2688 : }
    1037                 :            : 
    1038                 :       2688 : static void attach_free_list(MemoryTierShard& shard, MemoryFreeNode* head, MemoryFreeNode* tail) noexcept {
    1039         [ +  + ]:       2688 :   if (head == nullptr) {
    1040                 :       2129 :     return;
    1041                 :            :   }
    1042                 :            : 
    1043                 :        559 :   SpinLockGuard lock(shard.mtx);
    1044                 :            : 
    1045                 :        559 :   tail->next = head_of(shard);
    1046                 :        559 :   set_head(shard, head);
    1047                 :        559 : }
    1048                 :            : 
    1049                 :        330 : void MemoryPool::clear() noexcept {
    1050         [ +  + ]:        692 :   for (auto& state : impl_->owned_states) {
    1051                 :        362 :     std::lock_guard clear_lock(state->clear_mtx);
    1052                 :        362 :     std::vector<MemoryChunkTally> tally;
    1053                 :            : 
    1054                 :            :     try {
    1055         [ +  - ]:        362 :       std::lock_guard grow_lock(state->grow_mtx);
    1056                 :            : 
    1057         [ +  - ]:        362 :       std::sort(state->chunks.begin(), state->chunks.end(), [](const MemoryChunk& a, const MemoryChunk& b) noexcept {
    1058                 :        347 :         return reinterpret_cast<std::uintptr_t>(a.ptr) < reinterpret_cast<std::uintptr_t>(b.ptr);
    1059                 :            :       });
    1060                 :            : 
    1061         [ +  - ]:        362 :       tally.reserve(state->chunks.size());
    1062                 :            : 
    1063         [ +  + ]:        892 :       for (const MemoryChunk& chunk : state->chunks) {
    1064         [ +  - ]:        530 :         tally.push_back(MemoryChunkTally{chunk, 0U});
    1065                 :            :       }
    1066         [ -  - ]:        362 :     } catch (std::exception&) {
    1067                 :            :       continue;  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
    1068                 :          0 :     }
    1069                 :            : 
    1070         [ +  + ]:        362 :     if (tally.empty()) {
    1071                 :         26 :       continue;
    1072                 :            :     }
    1073                 :            : 
    1074                 :        336 :     MemoryFreeNode* heads[kMaxTierShardCount] = {};
    1075                 :            : 
    1076         [ +  + ]:       3024 :     for (size_t s = 0U; s < state->shard_count; ++s) {
    1077                 :       2688 :       heads[s] = detach_free_list(state->shards[s]);
    1078                 :            :     }
    1079                 :            : 
    1080                 :        336 :     const size_t block_size = state->block_size;
    1081                 :            : 
    1082                 :     658696 :     const auto find_tally = [&tally](const void* p) noexcept -> MemoryChunkTally* {
    1083                 :     140302 :       const auto addr = reinterpret_cast<std::uintptr_t>(p);
    1084                 :     140302 :       size_t lo = 0;
    1085                 :     140302 :       size_t hi = tally.size();
    1086                 :            : 
    1087         [ +  - ]:     189046 :       while (lo < hi) {
    1088                 :     189046 :         const size_t mid = lo + (hi - lo) / 2U;
    1089                 :     189046 :         const auto cs = reinterpret_cast<std::uintptr_t>(tally[mid].chunk.ptr);
    1090                 :     189046 :         const auto ce = cs + tally[mid].chunk.bytes;
    1091                 :            : 
    1092         [ +  + ]:     189046 :         if (addr < cs) {
    1093                 :      42676 :           hi = mid;
    1094         [ +  + ]:     146370 :         } else if (addr >= ce) {
    1095                 :       6068 :           lo = mid + 1U;
    1096                 :            :         } else {
    1097                 :     140302 :           return &tally[mid];
    1098                 :            :         }
    1099                 :            :       }
    1100                 :            : 
    1101                 :          0 :       return nullptr;
    1102                 :        336 :     };
    1103                 :            : 
    1104                 :     142422 :     const auto is_free = [block_size](const MemoryChunkTally* t) noexcept {
    1105   [ +  -  +  + ]:      71211 :       return t != nullptr && t->free_nodes == t->chunk.bytes / block_size;
    1106                 :        336 :     };
    1107                 :            : 
    1108         [ +  + ]:       3024 :     for (size_t s = 0U; s < state->shard_count; ++s) {
    1109         [ +  + ]:      72839 :       for (MemoryFreeNode* node = heads[s]; node != nullptr; node = node->next) {
    1110                 :      70151 :         MemoryChunkTally* t = find_tally(node);
    1111                 :            : 
    1112         [ +  - ]:      70151 :         if VLIKELY (t != nullptr) {
    1113                 :      70151 :           ++t->free_nodes;
    1114                 :            :         }
    1115                 :            :       }
    1116                 :            :     }
    1117                 :            : 
    1118         [ +  + ]:       3024 :     for (size_t s = 0U; s < state->shard_count; ++s) {
    1119                 :       2688 :       MemoryFreeNode* kept_head = nullptr;
    1120                 :       2688 :       MemoryFreeNode* kept_tail = nullptr;
    1121                 :       2688 :       MemoryFreeNode* current = heads[s];
    1122                 :            : 
    1123         [ +  + ]:      72839 :       while (current != nullptr) {
    1124                 :      70151 :         MemoryFreeNode* next = current->next;
    1125                 :            : 
    1126         [ +  + ]:      70151 :         if (!is_free(find_tally(current))) {
    1127                 :      44134 :           current->next = kept_head;
    1128                 :      44134 :           kept_head = current;
    1129                 :            : 
    1130         [ +  + ]:      44134 :           if (kept_tail == nullptr) {
    1131                 :        559 :             kept_tail = current;
    1132                 :            :           }
    1133                 :            :         }
    1134                 :            : 
    1135                 :      70151 :         current = next;
    1136                 :            :       }
    1137                 :            : 
    1138                 :       2688 :       attach_free_list(state->shards[s], kept_head, kept_tail);
    1139                 :            :     }
    1140                 :            : 
    1141                 :        336 :     size_t released = 0U;
    1142                 :            : 
    1143                 :            :     {
    1144                 :        336 :       std::lock_guard grow_lock(state->grow_mtx);
    1145                 :        336 :       std::vector<MemoryChunk>& live = state->chunks;
    1146                 :        336 :       size_t write = 0U;
    1147                 :            : 
    1148         [ +  + ]:       1057 :       for (size_t read = 0U; read < live.size(); ++read) {
    1149   [ +  +  +  +  :        721 :         if (read < tally.size() && is_free(&tally[read])) {
                   +  + ]
    1150                 :        202 :           ++released;
    1151                 :        202 :           continue;
    1152                 :            :         }
    1153                 :            : 
    1154                 :        519 :         live[write] = live[read];
    1155                 :        519 :         ++write;
    1156                 :            :       }
    1157                 :            : 
    1158                 :        336 :       live.resize(write);
    1159                 :        336 :       state->chunk_count.fetch_sub(released, std::memory_order_relaxed);
    1160                 :        336 :     }
    1161                 :            : 
    1162         [ +  + ]:        866 :     for (const MemoryChunkTally& t : tally) {
    1163         [ +  + ]:        530 :       if (is_free(&t)) {
    1164                 :        202 :         ::operator delete(t.chunk.ptr, t.chunk.bytes, std::align_val_t{kBlockAlignment});
    1165                 :            :       }
    1166                 :            :     }
    1167   [ +  +  +  + ]:        388 :   }
    1168                 :        330 : }
    1169                 :            : 
    1170                 :          2 : void MemoryPool::trim() noexcept { clear(); }
    1171                 :            : 
    1172                 :        236 : MemoryPool::Config MemoryPool::get_default_config() {
    1173                 :        222 :   static int level = []() noexcept {
    1174                 :        444 :     const std::string env_value = Utils::get_env("VLINK_MEMORY_LEVEL", "3");
    1175                 :            : 
    1176                 :        222 :     int parsed = kDefaultMemoryLevel;
    1177                 :            : 
    1178                 :        222 :     const char* first = env_value.data();
    1179                 :        222 :     const char* last = first + env_value.size();
    1180                 :            : 
    1181                 :        222 :     auto [ptr, ec] = std::from_chars(first, last, parsed);
    1182                 :            : 
    1183   [ +  +  -  +  :        222 :     if VUNLIKELY (ec != std::errc() || ptr != last) {
                   +  + ]
    1184                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1185                 :            :       MEMORY_POOL_LOG("MemoryPool: VLINK_MEMORY_LEVEL=\"%s\" is not a valid integer, fallback to %d.",
    1186                 :            :                       env_value.c_str(), kDefaultMemoryLevel);
    1187                 :            : 
    1188                 :            :       return kDefaultMemoryLevel;
    1189                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1190                 :            :     }
    1191                 :            : 
    1192   [ +  -  -  +  :        203 :     if VUNLIKELY (parsed < kMinMemoryLevel || parsed > kMaxMemoryLevel) {
                   -  + ]
    1193                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1194                 :            :       MEMORY_POOL_LOG("MemoryPool: VLINK_MEMORY_LEVEL=%d out of range [%d, %d], clamped.", parsed, kMinMemoryLevel,
    1195                 :            :                       kMaxMemoryLevel);
    1196                 :            : 
    1197                 :            :       return parsed < kMinMemoryLevel ? kMinMemoryLevel : kMaxMemoryLevel;
    1198                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1199                 :            :     }
    1200                 :            : 
    1201                 :        203 :     return parsed;
    1202   [ +  +  +  - ]:        458 :   }();
    1203                 :            : 
    1204   [ +  +  +  -  :        236 :   static bool prealloc_env = (Utils::get_env("VLINK_MEMORY_PREALLOC") == "1");
          +  -  +  -  -  
                      - ]
    1205   [ +  +  +  -  :        236 :   static bool lazy_scale_env = (Utils::get_env("VLINK_MEMORY_LAZY_SCALE") == "1");
          +  -  +  -  -  
                      - ]
    1206                 :            : 
    1207                 :        222 :   static size_t batch_size = []() noexcept {
    1208                 :        444 :     const std::string env_value = Utils::get_env("VLINK_MEMORY_BATCH_SIZE", "16");
    1209                 :        222 :     size_t parsed = kDefaultBatchSize;
    1210                 :            : 
    1211                 :        222 :     const char* first = env_value.data();
    1212                 :        222 :     const char* last = first + env_value.size();
    1213                 :        222 :     auto [ptr, ec] = std::from_chars(first, last, parsed);
    1214                 :            : 
    1215   [ +  +  -  +  :        222 :     if VUNLIKELY (ec != std::errc() || ptr != last || parsed == 0U) {
          +  +  +  +  +  
                      + ]
    1216                 :            :       // LCOV_EXCL_START GCOVR_EXCL_START
    1217                 :            :       MEMORY_POOL_LOG("MemoryPool: VLINK_MEMORY_BATCH_SIZE=\"%s\" is not a positive integer, fallback to %zu.",
    1218                 :            :                       env_value.c_str(), kDefaultBatchSize);
    1219                 :            : 
    1220                 :            :       return kDefaultBatchSize;
    1221                 :            :       // LCOV_EXCL_STOP GCOVR_EXCL_STOP
    1222                 :            :     }
    1223                 :            : 
    1224                 :        201 :     return parsed;
    1225   [ +  +  +  - ]:        458 :   }();
    1226                 :            : 
    1227                 :        236 :   Config config = create_memory_config(level, prealloc_env);
    1228                 :        236 :   config.batch_size = batch_size;
    1229                 :        236 :   config.lazy_scale = lazy_scale_env;
    1230                 :            : 
    1231                 :        236 :   return config;
    1232                 :            : }
    1233                 :            : 
    1234                 :     102529 : MemoryPool& MemoryPool::global_instance(bool use_env_level) {
    1235                 :            : #if MEMORY_POOL_NEVER_DELETE
    1236                 :            :   alignas(MemoryPool) static char buf[sizeof(MemoryPool)];
    1237                 :            : 
    1238                 :            :   static auto* instance =
    1239                 :            :       new (buf) MemoryPool(use_env_level ? get_default_config() : create_memory_config(kDefaultMemoryLevel, false));
    1240                 :            : 
    1241                 :            :   return *instance;
    1242                 :            : #else
    1243   [ +  +  +  -  :     102529 :   static MemoryPool instance(use_env_level ? get_default_config() : create_memory_config(kDefaultMemoryLevel, false));
          +  -  +  -  -  
             -  +  -  -  
                      - ]
    1244                 :            : 
    1245                 :     102530 :   return instance;
    1246                 :            : #endif
    1247                 :            : }
    1248                 :            : 
    1249                 :     716150 : size_t MemoryPool::find_tier(size_t bytes) const noexcept {
    1250                 :     716150 :   const size_t count = impl_->dispatch_count;
    1251                 :     716015 :   const size_t* const sizes = impl_->dispatch_max_sizes;
    1252                 :            : 
    1253         [ +  + ]:    1594005 :   for (size_t i = 0; i < count; ++i) {
    1254         [ +  + ]:    1593989 :     if (bytes <= sizes[i]) {
    1255                 :     715988 :       return i;
    1256                 :            :     }
    1257                 :            :   }
    1258                 :            : 
    1259                 :         16 :   return kMaxTierCount;
    1260                 :            : }
    1261                 :            : 
    1262                 :            : }  // namespace vlink

Generated by: LCOV version 1.14