aligned_page_pool.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. #include "aligned_page_pool.h"
  2. #include <util/generic/yexception.h>
  3. #include <util/stream/file.h>
  4. #include <util/string/cast.h>
  5. #include <util/string/strip.h>
  6. #include <util/system/align.h>
  7. #include <util/system/compiler.h>
  8. #include <util/system/error.h>
  9. #include <util/system/info.h>
  10. #include <util/thread/lfstack.h>
  11. #if defined(_win_)
  12. # include <util/system/winint.h>
  13. #elif defined(_unix_)
  14. # include <sys/types.h>
  15. # include <sys/mman.h>
  16. #endif
  17. namespace NKikimr {
  18. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  19. # if defined(PROFILE_MEMORY_ALLOCATIONS)
  20. static bool IsDefaultAllocator = true;
  21. # else
  22. static bool IsDefaultAllocator = false;
  23. # endif
  24. void UseDefaultAllocator() {
  25. // TODO: check that we didn't already used the MKQL allocator
  26. IsDefaultAllocator = true;
  27. }
  28. #endif
  29. static ui64 SYS_PAGE_SIZE = NSystemInfo::GetPageSize();
  30. constexpr ui32 MidLevels = 10;
  31. constexpr ui32 MaxMidSize = (1u << MidLevels) * TAlignedPagePool::POOL_PAGE_SIZE;
  32. static_assert(MaxMidSize == 64 * 1024 * 1024, "Upper memory block 64 Mb");
  33. namespace {
  34. ui64 GetMaxMemoryMaps() {
  35. ui64 maxMapCount = 0;
  36. #if defined(_unix_)
  37. maxMapCount = FromString<ui64>(Strip(TFileInput("/proc/sys/vm/max_map_count").ReadAll()));
  38. #endif
  39. return maxMapCount;
  40. }
  41. TString GetMemoryMapsString() {
  42. TStringStream ss;
  43. ss << " (maps: " << GetMemoryMapsCount() << " vs " << GetMaxMemoryMaps() << ")";
  44. return ss.Str();
  45. }
  46. template<typename T, bool SysAlign>
  47. class TGlobalPools;
  48. template<typename T, bool SysAlign>
  49. class TGlobalPagePool {
  50. friend class TGlobalPools<T, SysAlign>;
  51. public:
  52. TGlobalPagePool(size_t pageSize)
  53. : PageSize(pageSize)
  54. {}
  55. ~TGlobalPagePool() {
  56. void* addr = nullptr;
  57. while (Pages.Dequeue(&addr)) {
  58. FreePage(addr);
  59. }
  60. }
  61. void* GetPage() {
  62. void *page = nullptr;
  63. if (Pages.Dequeue(&page)) {
  64. --Count;
  65. return page;
  66. }
  67. return nullptr;
  68. }
  69. ui64 GetPageCount() const {
  70. return Count.load(std::memory_order_relaxed);
  71. }
  72. size_t GetPageSize() const {
  73. return PageSize;
  74. }
  75. size_t GetSize() const {
  76. return GetPageCount() * GetPageSize();
  77. }
  78. private:
  79. size_t PushPage(void* addr) {
  80. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  81. if (Y_UNLIKELY(IsDefaultAllocator)) {
  82. FreePage(addr);
  83. return GetPageSize();
  84. }
  85. #endif
  86. ++Count;
  87. Pages.Enqueue(addr);
  88. return 0;
  89. }
  90. void FreePage(void* addr) {
  91. auto res = T::Munmap(addr, PageSize);
  92. Y_DEBUG_ABORT_UNLESS(0 == res, "Madvise failed: %s", LastSystemErrorText());
  93. }
  94. private:
  95. const size_t PageSize;
  96. std::atomic<ui64> Count = 0;
  97. TLockFreeStack<void*> Pages;
  98. };
  99. template<typename T, bool SysAlign>
  100. class TGlobalPools {
  101. public:
  102. static TGlobalPools<T, SysAlign>& Instance() {
  103. return *Singleton<TGlobalPools<T, SysAlign>>();
  104. }
  105. TGlobalPagePool<T, SysAlign>& Get(ui32 index) {
  106. return *Pools[index];
  107. }
  108. const TGlobalPagePool<T, SysAlign>& Get(ui32 index) const {
  109. return *Pools[index];
  110. }
  111. TGlobalPools()
  112. {
  113. Reset();
  114. }
  115. void* DoMmap(size_t size) {
  116. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  117. // No memory maps allowed while using default allocator
  118. Y_DEBUG_ABORT_UNLESS(!IsDefaultAllocator);
  119. #endif
  120. void* res = T::Mmap(size);
  121. TotalMmappedBytes += size;
  122. return res;
  123. }
  124. void DoCleanupFreeList(ui64 targetSize) {
  125. for(ui32 level = 0; level <= MidLevels; ++level) {
  126. auto& p = Get(level);
  127. const size_t pageSize = p.GetPageSize();
  128. while(p.GetSize() >= targetSize) {
  129. void* page = p.GetPage();
  130. if (!page)
  131. break;
  132. p.FreePage(page);
  133. i64 prev = TotalMmappedBytes.fetch_sub(pageSize);
  134. Y_DEBUG_ABORT_UNLESS(prev >= 0);
  135. }
  136. }
  137. }
  138. void PushPage(size_t level, void* addr) {
  139. auto& pool = Get(level);
  140. size_t free = pool.PushPage(addr);
  141. if (Y_UNLIKELY(free > 0)) {
  142. i64 prev = TotalMmappedBytes.fetch_sub(free);
  143. Y_DEBUG_ABORT_UNLESS(prev >= 0);
  144. }
  145. }
  146. void DoMunmap(void* addr, size_t size) {
  147. if (Y_UNLIKELY(0 != T::Munmap(addr, size))) {
  148. TStringStream mmaps;
  149. const auto lastError = LastSystemError();
  150. if (lastError == ENOMEM) {
  151. mmaps << GetMemoryMapsString();
  152. }
  153. ythrow yexception() << "Munmap(0x"
  154. << IntToString<16>(reinterpret_cast<uintptr_t>(addr))
  155. << ", " << size << ") failed: " << LastSystemErrorText(lastError) << mmaps.Str();
  156. }
  157. i64 prev = TotalMmappedBytes.fetch_sub(size);
  158. Y_DEBUG_ABORT_UNLESS(prev >= 0);
  159. }
  160. i64 GetTotalMmappedBytes() const {
  161. return TotalMmappedBytes.load();
  162. }
  163. i64 GetTotalFreeListBytes() const {
  164. i64 bytes = 0;
  165. for (ui32 i = 0; i <= MidLevels; ++i) {
  166. bytes += Get(i).GetSize();
  167. }
  168. return bytes;
  169. }
  170. void Reset()
  171. {
  172. Pools.clear();
  173. Pools.reserve(MidLevels + 1);
  174. for (ui32 i = 0; i <= MidLevels; ++i) {
  175. Pools.emplace_back(MakeHolder<TGlobalPagePool<T, SysAlign>>(TAlignedPagePool::POOL_PAGE_SIZE << i));
  176. }
  177. }
  178. private:
  179. TVector<THolder<TGlobalPagePool<T, SysAlign>>> Pools;
  180. std::atomic<i64> TotalMmappedBytes{0};
  181. };
  182. } // unnamed
  183. #ifdef _win_
  184. #define MAP_FAILED (void*)(-1)
  185. inline void* TSystemMmap::Mmap(size_t size)
  186. {
  187. if (auto res = ::VirtualAlloc(0, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)) {
  188. return res;
  189. } else {
  190. return MAP_FAILED;
  191. }
  192. }
  193. inline int TSystemMmap::Munmap(void* addr, size_t size)
  194. {
  195. Y_ABORT_UNLESS(AlignUp(addr, SYS_PAGE_SIZE) == addr, "Got unaligned address");
  196. Y_ABORT_UNLESS(AlignUp(size, SYS_PAGE_SIZE) == size, "Got unaligned size");
  197. return !::VirtualFree(addr, size, MEM_DECOMMIT);
  198. }
  199. #else
  200. inline void* TSystemMmap::Mmap(size_t size)
  201. {
  202. return ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, 0, 0);
  203. }
  204. inline int TSystemMmap::Munmap(void* addr, size_t size)
  205. {
  206. Y_DEBUG_ABORT_UNLESS(AlignUp(addr, SYS_PAGE_SIZE) == addr, "Got unaligned address");
  207. Y_DEBUG_ABORT_UNLESS(AlignUp(size, SYS_PAGE_SIZE) == size, "Got unaligned size");
  208. if (size > MaxMidSize) {
  209. return ::munmap(addr, size);
  210. }
  211. // Unlock memory in case somewhere was called `mlockall(MCL_FUTURE)`.
  212. if (::munlock(addr, size) == -1) {
  213. switch(LastSystemError()) {
  214. case EAGAIN: [[fallthrough]];
  215. // The memory region was probably not locked - skip,
  216. // also since we can't distinguish from other kernel problems that may cause EAGAIN (not enough memory for structures?)
  217. // we rely on the failure of the following `madvise()` call.
  218. case EPERM: [[fallthrough]];
  219. // The most common case we get this error if we have no privileges, but also ignored error when called `mlockall()`
  220. // somewhere earlier. So ignore this.
  221. case EINVAL:
  222. // Something wrong with `addr` and `size` - we'll see the same error from the following `madvise()` call.
  223. break;
  224. case ENOMEM:
  225. // Locking or unlocking a region would result in the total number of mappings with distinct attributes
  226. // (e.g., locked versus unlocked) exceeding the allowed maximum.
  227. // NOTE: `madvise(MADV_DONTNEED)` can't return ENOMEM.
  228. return -1;
  229. }
  230. }
  231. /**
  232. There is at least a couple of drawbacks of using madvise instead of munmap:
  233. - more potential for use-after-free and memory corruption since we still may access unneeded regions by mistake,
  234. - actual RSS memory may be freed later after kernel gets some memory-pressure, and it may confuse system monitoring tools.
  235. But also there is a huge advantage: the number of memory maps used by process doesn't increase because of the "holes".
  236. The main source of the growth of number of memory regions is a clean-up of freed pages from page pools.
  237. Now we can safely invoke `TAlignedPagePool::DoCleanupGlobalFreeList()` whenever we want it.
  238. */
  239. return ::madvise(addr, size, MADV_DONTNEED);
  240. }
  241. #endif
  242. std::function<void(size_t size)> TFakeAlignedMmap::OnMmap = {};
  243. std::function<void(void* addr, size_t size)> TFakeAlignedMmap::OnMunmap = {};
  244. void* TFakeAlignedMmap::Mmap(size_t size)
  245. {
  246. if (OnMmap) {
  247. OnMmap(size);
  248. }
  249. return reinterpret_cast<void*>(TAlignedPagePool::POOL_PAGE_SIZE);
  250. }
  251. int TFakeAlignedMmap::Munmap(void* addr, size_t size)
  252. {
  253. if (OnMunmap) {
  254. OnMunmap(addr, size);
  255. }
  256. return 0;
  257. }
  258. std::function<void(size_t size)> TFakeUnalignedMmap::OnMmap = {};
  259. std::function<void(void* addr, size_t size)> TFakeUnalignedMmap::OnMunmap = {};
  260. void* TFakeUnalignedMmap::Mmap(size_t size)
  261. {
  262. if (OnMmap) {
  263. OnMmap(size);
  264. }
  265. return reinterpret_cast<void*>(TAlignedPagePool::POOL_PAGE_SIZE+1);
  266. }
  267. int TFakeUnalignedMmap::Munmap(void* addr, size_t size)
  268. {
  269. if (OnMunmap) {
  270. OnMunmap(addr, size);
  271. }
  272. return 0;
  273. }
  274. TAlignedPagePoolCounters::TAlignedPagePoolCounters(::NMonitoring::TDynamicCounterPtr countersRoot, const TString& name) {
  275. if (!countersRoot || name.empty())
  276. return;
  277. ::NMonitoring::TDynamicCounterPtr subGroup = countersRoot->GetSubgroup("counters", "utils")->GetSubgroup("subsystem", "mkqlalloc");
  278. TotalBytesAllocatedCntr = subGroup->GetCounter(name + "/TotalBytesAllocated");
  279. AllocationsCntr = subGroup->GetCounter(name + "/Allocations", true);
  280. PoolsCntr = subGroup->GetCounter(name + "/Pools", true);
  281. LostPagesBytesFreeCntr = subGroup->GetCounter(name + "/LostPagesBytesFreed", true);
  282. }
  283. template<typename T>
  284. TAlignedPagePoolImpl<T>::~TAlignedPagePoolImpl() {
  285. if (CheckLostMem && !UncaughtException()) {
  286. Y_DEBUG_ABORT_UNLESS(TotalAllocated == FreePages.size() * POOL_PAGE_SIZE,
  287. "memory leak; Expected %ld, actual %ld (%ld page(s), %ld offloaded); allocator created at: %s",
  288. TotalAllocated, FreePages.size() * POOL_PAGE_SIZE,
  289. FreePages.size(), OffloadedActiveBytes, GetDebugInfo().data());
  290. Y_DEBUG_ABORT_UNLESS(OffloadedActiveBytes == 0, "offloaded: %ld", OffloadedActiveBytes);
  291. }
  292. size_t activeBlocksSize = 0;
  293. for (auto it = ActiveBlocks.cbegin(); ActiveBlocks.cend() != it; ActiveBlocks.erase(it++)) {
  294. activeBlocksSize += it->second;
  295. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  296. if (Y_UNLIKELY(IsDefaultAllocator)) {
  297. ReturnBlock(it->first, it->second);
  298. return;
  299. }
  300. #endif
  301. Free(it->first, it->second);
  302. }
  303. if (activeBlocksSize > 0 || FreePages.size() != AllPages.size() || OffloadedActiveBytes) {
  304. if (Counters.LostPagesBytesFreeCntr) {
  305. (*Counters.LostPagesBytesFreeCntr) += OffloadedActiveBytes + activeBlocksSize + (AllPages.size() - FreePages.size()) * POOL_PAGE_SIZE;
  306. }
  307. }
  308. Y_DEBUG_ABORT_UNLESS(TotalAllocated == AllPages.size() * POOL_PAGE_SIZE + OffloadedActiveBytes,
  309. "Expected %ld, actual %ld (%ld page(s))", TotalAllocated,
  310. AllPages.size() * POOL_PAGE_SIZE + OffloadedActiveBytes, AllPages.size());
  311. for (auto &ptr : AllPages) {
  312. TGlobalPools<T, false>::Instance().PushPage(0, ptr);
  313. }
  314. if (Counters.TotalBytesAllocatedCntr) {
  315. (*Counters.TotalBytesAllocatedCntr) -= TotalAllocated;
  316. }
  317. if (Counters.PoolsCntr) {
  318. --(*Counters.PoolsCntr);
  319. }
  320. TotalAllocated = 0;
  321. }
  322. template<typename T>
  323. void TAlignedPagePoolImpl<T>::ReleaseFreePages() {
  324. TotalAllocated -= FreePages.size() * POOL_PAGE_SIZE;
  325. if (Counters.TotalBytesAllocatedCntr) {
  326. (*Counters.TotalBytesAllocatedCntr) -= FreePages.size() * POOL_PAGE_SIZE;
  327. }
  328. for (; !FreePages.empty(); FreePages.pop()) {
  329. AllPages.erase(FreePages.top());
  330. TGlobalPools<T, false>::Instance().PushPage(0, FreePages.top());
  331. }
  332. }
  333. template<typename T>
  334. void TAlignedPagePoolImpl<T>::OffloadAlloc(ui64 size) {
  335. if (Limit && TotalAllocated + size > Limit && !TryIncreaseLimit(TotalAllocated + size)) {
  336. throw TMemoryLimitExceededException();
  337. }
  338. if (AllocNotifyCallback) {
  339. if (AllocNotifyCurrentBytes > AllocNotifyBytes) {
  340. AllocNotifyCallback();
  341. AllocNotifyCurrentBytes = 0;
  342. }
  343. }
  344. ++OffloadedAllocCount;
  345. OffloadedBytes += size;
  346. OffloadedActiveBytes += size;
  347. TotalAllocated += size;
  348. if (AllocNotifyCallback) {
  349. AllocNotifyCurrentBytes += size;
  350. }
  351. if (Counters.TotalBytesAllocatedCntr) {
  352. (*Counters.TotalBytesAllocatedCntr) += size;
  353. }
  354. if (Counters.AllocationsCntr) {
  355. ++(*Counters.AllocationsCntr);
  356. }
  357. UpdatePeaks();
  358. }
  359. template<typename T>
  360. void TAlignedPagePoolImpl<T>::OffloadFree(ui64 size) noexcept {
  361. TotalAllocated -= size;
  362. OffloadedActiveBytes -= size;
  363. if (Counters.TotalBytesAllocatedCntr) {
  364. (*Counters.TotalBytesAllocatedCntr) -= size;
  365. }
  366. }
  367. template<typename T>
  368. void* TAlignedPagePoolImpl<T>::GetPage() {
  369. ++PageAllocCount;
  370. if (!FreePages.empty()) {
  371. ++PageHitCount;
  372. const auto res = FreePages.top();
  373. FreePages.pop();
  374. return res;
  375. }
  376. if (Limit && TotalAllocated + POOL_PAGE_SIZE > Limit && !TryIncreaseLimit(TotalAllocated + POOL_PAGE_SIZE)) {
  377. throw TMemoryLimitExceededException();
  378. }
  379. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  380. if (Y_LIKELY(!IsDefaultAllocator)) {
  381. #endif
  382. if (const auto ptr = TGlobalPools<T, false>::Instance().Get(0).GetPage()) {
  383. TotalAllocated += POOL_PAGE_SIZE;
  384. if (AllocNotifyCallback) {
  385. AllocNotifyCurrentBytes += POOL_PAGE_SIZE;
  386. }
  387. if (Counters.TotalBytesAllocatedCntr) {
  388. (*Counters.TotalBytesAllocatedCntr) += POOL_PAGE_SIZE;
  389. }
  390. ++PageGlobalHitCount;
  391. AllPages.emplace(ptr);
  392. UpdatePeaks();
  393. return ptr;
  394. }
  395. ++PageMissCount;
  396. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  397. }
  398. #endif
  399. void* res;
  400. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  401. if (Y_UNLIKELY(IsDefaultAllocator)) {
  402. res = GetBlock(POOL_PAGE_SIZE);
  403. } else {
  404. #endif
  405. res = Alloc(POOL_PAGE_SIZE);
  406. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  407. }
  408. #endif
  409. AllPages.emplace(res);
  410. return res;
  411. }
  412. template<typename T>
  413. void TAlignedPagePoolImpl<T>::ReturnPage(void* addr) noexcept {
  414. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  415. if (Y_UNLIKELY(IsDefaultAllocator)) {
  416. ReturnBlock(addr, POOL_PAGE_SIZE);
  417. AllPages.erase(addr);
  418. return;
  419. }
  420. #endif
  421. Y_DEBUG_ABORT_UNLESS(AllPages.find(addr) != AllPages.end());
  422. FreePages.emplace(addr);
  423. }
  424. template<typename T>
  425. void* TAlignedPagePoolImpl<T>::GetBlock(size_t size) {
  426. Y_DEBUG_ABORT_UNLESS(size >= POOL_PAGE_SIZE);
  427. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  428. if (Y_UNLIKELY(IsDefaultAllocator)) {
  429. OffloadAlloc(size);
  430. auto ret = malloc(size);
  431. if (!ret) {
  432. throw TMemoryLimitExceededException();
  433. }
  434. return ret;
  435. }
  436. #endif
  437. if (size == POOL_PAGE_SIZE) {
  438. return GetPage();
  439. } else {
  440. const auto ptr = Alloc(size);
  441. Y_DEBUG_ABORT_UNLESS(ActiveBlocks.emplace(ptr, size).second);
  442. return ptr;
  443. }
  444. }
  445. template<typename T>
  446. void TAlignedPagePoolImpl<T>::ReturnBlock(void* ptr, size_t size) noexcept {
  447. Y_DEBUG_ABORT_UNLESS(size >= POOL_PAGE_SIZE);
  448. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  449. if (Y_UNLIKELY(IsDefaultAllocator)) {
  450. OffloadFree(size);
  451. free(ptr);
  452. UpdateMemoryYellowZone();
  453. return;
  454. }
  455. #endif
  456. if (size == POOL_PAGE_SIZE) {
  457. ReturnPage(ptr);
  458. } else {
  459. Free(ptr, size);
  460. Y_DEBUG_ABORT_UNLESS(ActiveBlocks.erase(ptr));
  461. }
  462. UpdateMemoryYellowZone();
  463. }
  464. template<typename T>
  465. void* TAlignedPagePoolImpl<T>::Alloc(size_t size) {
  466. void* res = nullptr;
  467. size = AlignUp(size, SYS_PAGE_SIZE);
  468. if (Limit && TotalAllocated + size > Limit && !TryIncreaseLimit(TotalAllocated + size)) {
  469. throw TMemoryLimitExceededException();
  470. }
  471. if (AllocNotifyCallback) {
  472. if (AllocNotifyCurrentBytes > AllocNotifyBytes) {
  473. AllocNotifyCallback();
  474. AllocNotifyCurrentBytes = 0;
  475. }
  476. }
  477. auto& globalPool = TGlobalPools<T, false>::Instance();
  478. if (size > POOL_PAGE_SIZE && size <= MaxMidSize) {
  479. size = FastClp2(size);
  480. auto level = LeastSignificantBit(size) - LeastSignificantBit(POOL_PAGE_SIZE);
  481. Y_DEBUG_ABORT_UNLESS(level >= 1 && level <= MidLevels);
  482. if (res = globalPool.Get(level).GetPage()) {
  483. TotalAllocated += size;
  484. if (AllocNotifyCallback) {
  485. AllocNotifyCurrentBytes += size;
  486. }
  487. if (Counters.TotalBytesAllocatedCntr) {
  488. (*Counters.TotalBytesAllocatedCntr) += size;
  489. }
  490. ++PageGlobalHitCount;
  491. } else {
  492. ++PageMissCount;
  493. }
  494. }
  495. if (!res) {
  496. auto allocSize = size + ALLOC_AHEAD_PAGES * POOL_PAGE_SIZE;
  497. void* mem = globalPool.DoMmap(allocSize);
  498. if (Y_UNLIKELY(MAP_FAILED == mem)) {
  499. TStringStream mmaps;
  500. const auto lastError = LastSystemError();
  501. if (lastError == ENOMEM) {
  502. mmaps << GetMemoryMapsString();
  503. }
  504. ythrow yexception() << "Mmap failed to allocate " << (size + POOL_PAGE_SIZE) << " bytes: "
  505. << LastSystemErrorText(lastError) << mmaps.Str();
  506. }
  507. res = AlignUp(mem, POOL_PAGE_SIZE);
  508. const size_t off = reinterpret_cast<intptr_t>(res) - reinterpret_cast<intptr_t>(mem);
  509. if (Y_UNLIKELY(off)) {
  510. // unmap prefix
  511. globalPool.DoMunmap(mem, off);
  512. }
  513. // Extra space is also page-aligned. Put it to the free page list
  514. auto alignedSize = AlignUp(size, POOL_PAGE_SIZE);
  515. ui64 extraPages = (allocSize - off - alignedSize) / POOL_PAGE_SIZE;
  516. ui64 tail = (allocSize - off - alignedSize) % POOL_PAGE_SIZE;
  517. auto extraPage = reinterpret_cast<ui8*>(res) + alignedSize;
  518. for (ui64 i = 0; i < extraPages; ++i) {
  519. AllPages.emplace(extraPage);
  520. FreePages.emplace(extraPage);
  521. extraPage += POOL_PAGE_SIZE;
  522. }
  523. if (size != alignedSize) {
  524. // unmap unaligned hole
  525. globalPool.DoMunmap(reinterpret_cast<ui8*>(res) + size, alignedSize - size);
  526. }
  527. if (tail) {
  528. // unmap suffix
  529. Y_DEBUG_ABORT_UNLESS(extraPage+tail <= reinterpret_cast<ui8*>(mem) + size + ALLOC_AHEAD_PAGES * POOL_PAGE_SIZE);
  530. globalPool.DoMunmap(extraPage, tail);
  531. }
  532. auto extraSize = extraPages * POOL_PAGE_SIZE;
  533. auto totalSize = size + extraSize;
  534. TotalAllocated += totalSize;
  535. if (AllocNotifyCallback) {
  536. AllocNotifyCurrentBytes += totalSize;
  537. }
  538. if (Counters.TotalBytesAllocatedCntr) {
  539. (*Counters.TotalBytesAllocatedCntr) += totalSize;
  540. }
  541. }
  542. if (Counters.AllocationsCntr) {
  543. ++(*Counters.AllocationsCntr);
  544. }
  545. ++AllocCount;
  546. UpdatePeaks();
  547. return res;
  548. }
  549. template<typename T>
  550. void TAlignedPagePoolImpl<T>::Free(void* ptr, size_t size) noexcept {
  551. size = AlignUp(size, SYS_PAGE_SIZE);
  552. if (size <= MaxMidSize)
  553. size = FastClp2(size);
  554. if (size <= MaxMidSize) {
  555. auto level = LeastSignificantBit(size) - LeastSignificantBit(POOL_PAGE_SIZE);
  556. Y_DEBUG_ABORT_UNLESS(level >= 1 && level <= MidLevels);
  557. TGlobalPools<T, false>::Instance().PushPage(level, ptr);
  558. } else {
  559. TGlobalPools<T, false>::Instance().DoMunmap(ptr, size);
  560. }
  561. Y_DEBUG_ABORT_UNLESS(TotalAllocated >= size);
  562. TotalAllocated -= size;
  563. if (Counters.TotalBytesAllocatedCntr) {
  564. (*Counters.TotalBytesAllocatedCntr) -= size;
  565. }
  566. }
  567. template<typename T>
  568. void TAlignedPagePoolImpl<T>::DoCleanupGlobalFreeList(ui64 targetSize) {
  569. TGlobalPools<T, true>::Instance().DoCleanupFreeList(targetSize);
  570. TGlobalPools<T, false>::Instance().DoCleanupFreeList(targetSize);
  571. }
  572. template<typename T>
  573. void TAlignedPagePoolImpl<T>::UpdateMemoryYellowZone() {
  574. if (Limit == 0) return;
  575. if (IsMemoryYellowZoneForcefullyChanged) return;
  576. if (IncreaseMemoryLimitCallback && !IsMaximumLimitValueReached) return;
  577. ui8 usedMemoryPercent = 100 * GetUsed() / Limit;
  578. if (usedMemoryPercent >= EnableMemoryYellowZoneThreshold) {
  579. IsMemoryYellowZoneReached = true;
  580. } else if (usedMemoryPercent <= DisableMemoryYellowZoneThreshold) {
  581. IsMemoryYellowZoneReached = false;
  582. }
  583. }
  584. template<typename T>
  585. bool TAlignedPagePoolImpl<T>::TryIncreaseLimit(ui64 required) {
  586. if (!IncreaseMemoryLimitCallback) {
  587. return false;
  588. }
  589. IncreaseMemoryLimitCallback(Limit, required);
  590. return Limit >= required;
  591. }
  592. template<typename T>
  593. ui64 TAlignedPagePoolImpl<T>::GetGlobalPagePoolSize() {
  594. ui64 size = 0;
  595. for (size_t level = 0; level <= MidLevels; ++level) {
  596. size += TGlobalPools<T, false>::Instance().Get(level).GetSize();
  597. }
  598. return size;
  599. }
  600. template<typename T>
  601. void TAlignedPagePoolImpl<T>::PrintStat(size_t usedPages, IOutputStream& out) const {
  602. usedPages += GetFreePageCount();
  603. out << "Count of free pages: " << GetFreePageCount() << Endl;
  604. out << "Allocated for blocks: " << (GetAllocated() - usedPages * POOL_PAGE_SIZE) << Endl;
  605. out << "Total allocated by lists: " << GetAllocated() << Endl;
  606. }
  607. template<typename T>
  608. void TAlignedPagePoolImpl<T>::ResetGlobalsUT()
  609. {
  610. TGlobalPools<T, false>::Instance().Reset();
  611. }
  612. #if defined(ALLOW_DEFAULT_ALLOCATOR)
  613. // static
  614. template<typename T>
  615. bool TAlignedPagePoolImpl<T>::IsDefaultAllocatorUsed() {
  616. return IsDefaultAllocator;
  617. }
  618. #endif
  619. template class TAlignedPagePoolImpl<>;
  620. template class TAlignedPagePoolImpl<TFakeAlignedMmap>;
  621. template class TAlignedPagePoolImpl<TFakeUnalignedMmap>;
  622. template<typename TMmap>
  623. void* GetAlignedPage(ui64 size) {
  624. size = AlignUp(size, SYS_PAGE_SIZE);
  625. if (size < TAlignedPagePool::POOL_PAGE_SIZE) {
  626. size = TAlignedPagePool::POOL_PAGE_SIZE;
  627. }
  628. auto& pool = TGlobalPools<TMmap, true>::Instance();
  629. if (size <= MaxMidSize) {
  630. size = FastClp2(size);
  631. auto level = LeastSignificantBit(size) - LeastSignificantBit(TAlignedPagePool::POOL_PAGE_SIZE);
  632. Y_DEBUG_ABORT_UNLESS(level <= MidLevels);
  633. if (auto res = pool.Get(level).GetPage()) {
  634. return res;
  635. }
  636. }
  637. auto allocSize = Max<ui64>(MaxMidSize, size);
  638. void* mem = pool.DoMmap(allocSize);
  639. if (Y_UNLIKELY(MAP_FAILED == mem)) {
  640. TStringStream mmaps;
  641. const auto lastError = LastSystemError();
  642. if (lastError == ENOMEM) {
  643. mmaps << GetMemoryMapsString();
  644. }
  645. ythrow yexception() << "Mmap failed to allocate " << allocSize << " bytes: " << LastSystemErrorText(lastError) << mmaps.Str();
  646. }
  647. if (size < MaxMidSize) {
  648. // push extra allocated pages to cache
  649. auto level = LeastSignificantBit(size) - LeastSignificantBit(TAlignedPagePool::POOL_PAGE_SIZE);
  650. Y_DEBUG_ABORT_UNLESS(level <= MidLevels);
  651. ui8* ptr = (ui8*)mem + size;
  652. ui8* const end = (ui8*)mem + MaxMidSize;
  653. while (ptr < end) {
  654. pool.PushPage(level, ptr);
  655. ptr += size;
  656. }
  657. }
  658. return mem;
  659. }
  660. template<typename TMmap>
  661. void ReleaseAlignedPage(void* mem, ui64 size) {
  662. size = AlignUp(size, SYS_PAGE_SIZE);
  663. if (size < TAlignedPagePool::POOL_PAGE_SIZE) {
  664. size = TAlignedPagePool::POOL_PAGE_SIZE;
  665. }
  666. if (size <= MaxMidSize) {
  667. size = FastClp2(size);
  668. auto level = LeastSignificantBit(size) - LeastSignificantBit(TAlignedPagePool::POOL_PAGE_SIZE);
  669. Y_DEBUG_ABORT_UNLESS(level <= MidLevels);
  670. TGlobalPools<TMmap, true>::Instance().PushPage(level, mem);
  671. return;
  672. }
  673. TGlobalPools<TMmap, true>::Instance().DoMunmap(mem, size);
  674. }
  675. template<typename TMmap>
  676. i64 GetTotalMmapedBytes() {
  677. return TGlobalPools<TMmap, true>::Instance().GetTotalMmappedBytes() + TGlobalPools<TMmap, false>::Instance().GetTotalMmappedBytes();
  678. }
  679. template<typename TMmap>
  680. i64 GetTotalFreeListBytes() {
  681. return TGlobalPools<TMmap, true>::Instance().GetTotalFreeListBytes() + TGlobalPools<TMmap, false>::Instance().GetTotalFreeListBytes();
  682. }
  683. template i64 GetTotalMmapedBytes<>();
  684. template i64 GetTotalMmapedBytes<TFakeAlignedMmap>();
  685. template i64 GetTotalMmapedBytes<TFakeUnalignedMmap>();
  686. template i64 GetTotalFreeListBytes<>();
  687. template i64 GetTotalFreeListBytes<TFakeAlignedMmap>();
  688. template i64 GetTotalFreeListBytes<TFakeUnalignedMmap>();
  689. template void* GetAlignedPage<>(ui64);
  690. template void* GetAlignedPage<TFakeAlignedMmap>(ui64);
  691. template void* GetAlignedPage<TFakeUnalignedMmap>(ui64);
  692. template void ReleaseAlignedPage<>(void*,ui64);
  693. template void ReleaseAlignedPage<TFakeAlignedMmap>(void*,ui64);
  694. template void ReleaseAlignedPage<TFakeUnalignedMmap>(void*,ui64);
  695. size_t GetMemoryMapsCount() {
  696. size_t lineCount = 0;
  697. TString line;
  698. #if defined(_unix_)
  699. TFileInput file("/proc/self/maps");
  700. while (file.ReadLine(line)) ++lineCount;
  701. #endif
  702. return lineCount;
  703. }
  704. } // NKikimr