lf_allocX64.h 60 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931
  1. #pragma once
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <stdarg.h>
  5. #include <library/cpp/malloc/api/malloc.h>
  6. #include <util/system/compat.h>
  7. #include <util/system/compiler.h>
  8. #include <util/system/types.h>
  9. #ifdef _MSC_VER
  10. #ifndef _CRT_SECURE_NO_WARNINGS
  11. #define _CRT_SECURE_NO_WARNINGS
  12. #endif
  13. #ifdef _M_X64
  14. #define _64_
  15. #endif
  16. #include <intrin.h>
  17. #define WIN32_LEAN_AND_MEAN
  18. #include <Windows.h>
  19. #pragma intrinsic(_InterlockedCompareExchange)
  20. #pragma intrinsic(_InterlockedExchangeAdd)
  21. #include <new>
  22. #include <assert.h>
  23. #include <errno.h>
  24. #define PERTHREAD __declspec(thread)
  25. #define _win_
  26. #define Y_FORCE_INLINE __forceinline
  27. using TAtomic = volatile long;
  28. static inline long AtomicAdd(TAtomic& a, long b) {
  29. return _InterlockedExchangeAdd(&a, b) + b;
  30. }
  31. static inline long AtomicSub(TAtomic& a, long b) {
  32. return AtomicAdd(a, -b);
  33. }
  34. #pragma comment(lib, "synchronization.lib")
  35. #ifndef NDEBUG
  36. #define Y_ASSERT_NOBT(x) \
  37. { \
  38. if (IsDebuggerPresent()) { \
  39. if (!(x)) \
  40. __debugbreak(); \
  41. } else \
  42. assert(x); \
  43. }
  44. #else
  45. #define Y_ASSERT_NOBT(x) ((void)0)
  46. #endif
  47. #else
  48. #include <util/system/defaults.h>
  49. #include <library/cpp/deprecated/atomic/atomic.h>
  50. #include <util/system/yassert.h>
  51. #if !defined(NDEBUG) && !defined(__GCCXML__)
  52. #define Y_ASSERT_NOBT(a) \
  53. do { \
  54. try { \
  55. if (Y_UNLIKELY(!(a))) { \
  56. if (YaIsDebuggerPresent()) \
  57. __debugbreak(); \
  58. else { \
  59. assert(false && (a)); \
  60. } \
  61. } \
  62. } catch (...) { \
  63. if (YaIsDebuggerPresent()) \
  64. __debugbreak(); \
  65. else { \
  66. assert(false && "Exception during assert"); \
  67. } \
  68. } \
  69. } while (0)
  70. #else
  71. #define Y_ASSERT_NOBT(a) \
  72. do { \
  73. if (false) { \
  74. bool __xxx = static_cast<bool>(a); \
  75. Y_UNUSED(__xxx); \
  76. } \
  77. } while (0)
  78. #endif
  79. #include <pthread.h>
  80. #include <sys/mman.h>
  81. #include <stdlib.h>
  82. #include <memory.h>
  83. #include <new>
  84. #include <errno.h>
  85. #if defined(_linux_)
  86. #include <linux/futex.h>
  87. #include <sys/syscall.h>
  88. #if !defined(MADV_HUGEPAGE)
  89. #define MADV_HUGEPAGE 14
  90. #endif
  91. #if !defined(MAP_HUGETLB)
  92. #define MAP_HUGETLB 0x40000
  93. #endif
  94. #endif
  95. #define PERTHREAD __thread
  96. #endif
  97. #ifndef _darwin_
  98. #ifndef Y_ARRAY_SIZE
  99. #define Y_ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
  100. #endif
  101. #ifndef NDEBUG
  102. #define DBG_FILL_MEMORY
  103. static bool FillMemoryOnAllocation = true;
  104. #endif
  105. static bool TransparentHugePages = false; // force MADV_HUGEPAGE for large allocs
  106. static bool MapHugeTLB = false; // force MAP_HUGETLB for small allocs
  107. static bool EnableDefrag = true;
  108. // Buffers that are larger than this size will not be filled with 0xcf
  109. #ifndef DBG_FILL_MAX_SIZE
  110. #define DBG_FILL_MAX_SIZE 0x01000000000000ULL
  111. #endif
  112. template <class T>
  113. inline T* DoCas(T* volatile* target, T* exchange, T* compare) {
  114. #if defined(__has_builtin) && __has_builtin(__sync_val_compare_and_swap)
  115. return __sync_val_compare_and_swap(target, compare, exchange);
  116. #elif defined(_WIN32)
  117. #ifdef _64_
  118. return (T*)_InterlockedCompareExchange64((__int64*)target, (__int64)exchange, (__int64)compare);
  119. #else
  120. //return (T*)InterlockedCompareExchangePointer(targetVoidP, exchange, compare);
  121. return (T*)_InterlockedCompareExchange((LONG*)target, (LONG)exchange, (LONG)compare);
  122. #endif
  123. #elif defined(__i386) || defined(__x86_64__)
  124. union {
  125. T* volatile* NP;
  126. void* volatile* VoidP;
  127. } gccSucks;
  128. gccSucks.NP = target;
  129. void* volatile* targetVoidP = gccSucks.VoidP;
  130. __asm__ __volatile__(
  131. "lock\n\t"
  132. "cmpxchg %2,%0\n\t"
  133. : "+m"(*(targetVoidP)), "+a"(compare)
  134. : "r"(exchange)
  135. : "cc", "memory");
  136. return compare;
  137. #else
  138. #error inline_cas not defined for this platform
  139. #endif
  140. }
  141. #ifdef _64_
  142. const uintptr_t N_MAX_WORKSET_SIZE = 0x100000000ll * 200;
  143. const uintptr_t N_HUGE_AREA_FINISH = 0x700000000000ll;
  144. #ifndef _freebsd_
  145. const uintptr_t LINUX_MMAP_AREA_START = 0x100000000ll;
  146. static uintptr_t volatile linuxAllocPointer = LINUX_MMAP_AREA_START;
  147. static uintptr_t volatile linuxAllocPointerHuge = LINUX_MMAP_AREA_START + N_MAX_WORKSET_SIZE;
  148. #endif
  149. #else
  150. const uintptr_t N_MAX_WORKSET_SIZE = 0xffffffff;
  151. #endif
  152. #define ALLOC_START ((char*)0)
  153. const size_t N_CHUNK_SIZE = 1024 * 1024;
  154. const size_t N_CHUNKS = N_MAX_WORKSET_SIZE / N_CHUNK_SIZE;
  155. const size_t N_LARGE_ALLOC_SIZE = N_CHUNK_SIZE * 128;
  156. // map size idx to size in bytes
  157. #ifdef LFALLOC_YT
  158. const int N_SIZES = 27;
  159. #else
  160. const int N_SIZES = 25;
  161. #endif
  162. const int nSizeIdxToSize[N_SIZES] = {
  163. -1,
  164. #if defined(_64_)
  165. 16, 16, 32, 32, 48, 64, 96, 128,
  166. #else
  167. 8,
  168. 16,
  169. 24,
  170. 32,
  171. 48,
  172. 64,
  173. 96,
  174. 128,
  175. #endif
  176. 192, 256, 384, 512, 768, 1024, 1536, 2048,
  177. 3072, 4096, 6144, 8192, 12288, 16384, 24576, 32768,
  178. #ifdef LFALLOC_YT
  179. 49152, 65536
  180. #endif
  181. };
  182. #ifdef LFALLOC_YT
  183. const size_t N_MAX_FAST_SIZE = 65536;
  184. #else
  185. const size_t N_MAX_FAST_SIZE = 32768;
  186. #endif
  187. const unsigned char size2idxArr1[64 + 1] = {
  188. 1,
  189. #if defined(_64_)
  190. 2, 2, 4, 4, // 16, 16, 32, 32
  191. #else
  192. 1, 2, 3, 4, // 8, 16, 24, 32
  193. #endif
  194. 5, 5, 6, 6, // 48, 64
  195. 7, 7, 7, 7, 8, 8, 8, 8, // 96, 128
  196. 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 10, 10, 10, // 192, 256
  197. 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, // 384
  198. 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12 // 512
  199. };
  200. #ifdef LFALLOC_YT
  201. const unsigned char size2idxArr2[256] = {
  202. #else
  203. const unsigned char size2idxArr2[128] = {
  204. #endif
  205. 12, 12, 13, 14, // 512, 512, 768, 1024
  206. 15, 15, 16, 16, // 1536, 2048
  207. 17, 17, 17, 17, 18, 18, 18, 18, // 3072, 4096
  208. 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20, 20, 20, 20, 20, 20, // 6144, 8192
  209. 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, // 12288
  210. 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, // 16384
  211. 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
  212. 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, // 24576
  213. 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
  214. 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, // 32768
  215. #ifdef LFALLOC_YT
  216. 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
  217. 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
  218. 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
  219. 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, // 49152
  220. 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
  221. 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
  222. 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
  223. 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, // 65536
  224. #endif
  225. };
  226. // map entry number to size idx
  227. // special size idx's: 0 = not used, -1 = mem locked, but not allocated
  228. static volatile char chunkSizeIdx[N_CHUNKS];
  229. const int FREE_CHUNK_ARR_BUF = 0x20000; // this is effectively 128G of free memory (with 1M chunks), should not be exhausted actually
  230. static volatile uintptr_t freeChunkArr[FREE_CHUNK_ARR_BUF];
  231. static volatile int freeChunkCount;
  232. static void AddFreeChunk(uintptr_t chunkId) {
  233. chunkSizeIdx[chunkId] = -1;
  234. if (Y_UNLIKELY(freeChunkCount == FREE_CHUNK_ARR_BUF))
  235. NMalloc::AbortFromCorruptedAllocator("free chunks array overflowed");
  236. freeChunkArr[freeChunkCount++] = chunkId;
  237. }
  238. static bool GetFreeChunk(uintptr_t* res) {
  239. if (freeChunkCount == 0) {
  240. *res = 0;
  241. return false;
  242. }
  243. *res = freeChunkArr[--freeChunkCount];
  244. return true;
  245. }
  246. //////////////////////////////////////////////////////////////////////////
  247. enum ELFAllocCounter {
  248. CT_USER_ALLOC, // accumulated size requested by user code
  249. CT_MMAP, // accumulated mmapped size
  250. CT_MMAP_CNT, // number of mmapped regions
  251. CT_MUNMAP, // accumulated unmmapped size
  252. CT_MUNMAP_CNT, // number of munmaped regions
  253. CT_SYSTEM_ALLOC, // accumulated allocated size for internal lfalloc needs
  254. CT_SYSTEM_FREE, // accumulated deallocated size for internal lfalloc needs
  255. CT_SMALL_ALLOC, // accumulated allocated size for fixed-size blocks
  256. CT_SMALL_FREE, // accumulated deallocated size for fixed-size blocks
  257. CT_LARGE_ALLOC, // accumulated allocated size for large blocks
  258. CT_LARGE_FREE, // accumulated deallocated size for large blocks
  259. CT_SLOW_ALLOC_CNT, // number of slow (not LF) allocations
  260. CT_DEGRAGMENT_CNT, // number of memory defragmentations
  261. CT_MAX
  262. };
  263. static Y_FORCE_INLINE void IncrementCounter(ELFAllocCounter counter, size_t value);
  264. //////////////////////////////////////////////////////////////////////////
  265. enum EMMapMode {
  266. MM_NORMAL, // memory for small allocs
  267. MM_HUGE // memory for large allocs
  268. };
  269. #ifndef _MSC_VER
  270. inline void VerifyMmapResult(void* result) {
  271. if (Y_UNLIKELY(result == MAP_FAILED))
  272. NMalloc::AbortFromCorruptedAllocator("negative size requested? or just out of mem");
  273. }
  274. #endif
  275. #if !defined(_MSC_VER) && !defined(_freebsd_) && defined(_64_)
  276. static char* AllocWithMMapLinuxImpl(uintptr_t sz, EMMapMode mode) {
  277. char* volatile* areaPtr;
  278. char* areaStart;
  279. uintptr_t areaFinish;
  280. int mapProt = PROT_READ | PROT_WRITE;
  281. int mapFlags = MAP_PRIVATE | MAP_ANON;
  282. if (mode == MM_HUGE) {
  283. areaPtr = reinterpret_cast<char* volatile*>(&linuxAllocPointerHuge);
  284. areaStart = reinterpret_cast<char*>(LINUX_MMAP_AREA_START + N_MAX_WORKSET_SIZE);
  285. areaFinish = N_HUGE_AREA_FINISH;
  286. } else {
  287. areaPtr = reinterpret_cast<char* volatile*>(&linuxAllocPointer);
  288. areaStart = reinterpret_cast<char*>(LINUX_MMAP_AREA_START);
  289. areaFinish = N_MAX_WORKSET_SIZE;
  290. if (MapHugeTLB) {
  291. mapFlags |= MAP_HUGETLB;
  292. }
  293. }
  294. bool wrapped = false;
  295. for (;;) {
  296. char* prevAllocPtr = *areaPtr;
  297. char* nextAllocPtr = prevAllocPtr + sz;
  298. if (uintptr_t(nextAllocPtr - (char*)nullptr) >= areaFinish) {
  299. if (Y_UNLIKELY(wrapped)) {
  300. NMalloc::AbortFromCorruptedAllocator("virtual memory is over fragmented");
  301. }
  302. // wrap after all area is used
  303. DoCas(areaPtr, areaStart, prevAllocPtr);
  304. wrapped = true;
  305. continue;
  306. }
  307. if (DoCas(areaPtr, nextAllocPtr, prevAllocPtr) != prevAllocPtr)
  308. continue;
  309. char* largeBlock = (char*)mmap(prevAllocPtr, sz, mapProt, mapFlags, -1, 0);
  310. VerifyMmapResult(largeBlock);
  311. if (largeBlock == prevAllocPtr)
  312. return largeBlock;
  313. if (largeBlock)
  314. munmap(largeBlock, sz);
  315. if (sz < 0x80000) {
  316. // skip utilized area with big steps
  317. DoCas(areaPtr, nextAllocPtr + 0x10 * 0x10000, nextAllocPtr);
  318. }
  319. }
  320. }
  321. #endif
  322. static char* AllocWithMMap(uintptr_t sz, EMMapMode mode) {
  323. (void)mode;
  324. #ifdef _MSC_VER
  325. char* largeBlock = (char*)VirtualAlloc(0, sz, MEM_RESERVE, PAGE_READWRITE);
  326. if (Y_UNLIKELY(largeBlock == nullptr))
  327. NMalloc::AbortFromCorruptedAllocator("out of memory");
  328. if (Y_UNLIKELY(uintptr_t(((char*)largeBlock - ALLOC_START) + sz) >= N_MAX_WORKSET_SIZE))
  329. NMalloc::AbortFromCorruptedAllocator("out of working set, something has broken");
  330. #else
  331. #if defined(_freebsd_) || !defined(_64_)
  332. char* largeBlock = (char*)mmap(0, sz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
  333. VerifyMmapResult(largeBlock);
  334. if (Y_UNLIKELY(uintptr_t(((char*)largeBlock - ALLOC_START) + sz) >= N_MAX_WORKSET_SIZE))
  335. NMalloc::AbortFromCorruptedAllocator("out of working set, something has broken");
  336. #else
  337. char* largeBlock = AllocWithMMapLinuxImpl(sz, mode);
  338. if (TransparentHugePages) {
  339. madvise(largeBlock, sz, MADV_HUGEPAGE);
  340. }
  341. #endif
  342. #endif
  343. Y_ASSERT_NOBT(largeBlock);
  344. IncrementCounter(CT_MMAP, sz);
  345. IncrementCounter(CT_MMAP_CNT, 1);
  346. return largeBlock;
  347. }
  348. enum class ELarge : ui8 {
  349. Free = 0, // block in free cache
  350. Alloc = 1, // block is allocated
  351. Gone = 2, // block was unmapped
  352. };
  353. struct TLargeBlk {
  354. static TLargeBlk* As(void *raw) {
  355. return reinterpret_cast<TLargeBlk*>((char*)raw - 4096ll);
  356. }
  357. static const TLargeBlk* As(const void *raw) {
  358. return reinterpret_cast<const TLargeBlk*>((const char*)raw - 4096ll);
  359. }
  360. void SetSize(size_t bytes, size_t pages) {
  361. Pages = pages;
  362. Bytes = bytes;
  363. }
  364. void Mark(ELarge state) {
  365. const ui64 marks[] = {
  366. 0x8b38aa5ca4953c98, // ELarge::Free
  367. 0xf916d33584eb5087, // ELarge::Alloc
  368. 0xd33b0eca7651bc3f // ELarge::Gone
  369. };
  370. Token = size_t(marks[ui8(state)]);
  371. }
  372. size_t Pages; // Total pages allocated with mmap like call
  373. size_t Bytes; // Actually requested bytes by user
  374. size_t Token; // Block state token, see ELarge enum.
  375. };
  376. static void LargeBlockUnmap(void* p, size_t pages) {
  377. const auto bytes = (pages + 1) * uintptr_t(4096);
  378. IncrementCounter(CT_MUNMAP, bytes);
  379. IncrementCounter(CT_MUNMAP_CNT, 1);
  380. #ifdef _MSC_VER
  381. Y_ASSERT_NOBT(0);
  382. #else
  383. TLargeBlk::As(p)->Mark(ELarge::Gone);
  384. munmap((char*)p - 4096ll, bytes);
  385. #endif
  386. }
  387. //////////////////////////////////////////////////////////////////////////
  388. const size_t LB_BUF_SIZE = 250;
  389. const size_t LB_BUF_HASH = 977;
  390. static int LB_LIMIT_TOTAL_SIZE = 500 * 1024 * 1024 / 4096; // do not keep more then this mem total in lbFreePtrs[]
  391. static void* volatile lbFreePtrs[LB_BUF_HASH][LB_BUF_SIZE];
  392. static TAtomic lbFreePageCount;
  393. static void* LargeBlockAlloc(size_t _nSize, ELFAllocCounter counter) {
  394. size_t pgCount = (_nSize + 4095) / 4096;
  395. #ifdef _MSC_VER
  396. char* pRes = (char*)VirtualAlloc(0, (pgCount + 1) * 4096ll, MEM_COMMIT, PAGE_READWRITE);
  397. if (Y_UNLIKELY(pRes == 0)) {
  398. NMalloc::AbortFromCorruptedAllocator("out of memory");
  399. }
  400. #else
  401. IncrementCounter(counter, pgCount * 4096ll);
  402. IncrementCounter(CT_SYSTEM_ALLOC, 4096ll);
  403. int lbHash = pgCount % LB_BUF_HASH;
  404. for (int i = 0; i < LB_BUF_SIZE; ++i) {
  405. void* p = lbFreePtrs[lbHash][i];
  406. if (p == nullptr)
  407. continue;
  408. if (DoCas(&lbFreePtrs[lbHash][i], (void*)nullptr, p) == p) {
  409. size_t realPageCount = TLargeBlk::As(p)->Pages;
  410. if (realPageCount == pgCount) {
  411. AtomicAdd(lbFreePageCount, -pgCount);
  412. TLargeBlk::As(p)->Mark(ELarge::Alloc);
  413. return p;
  414. } else {
  415. if (DoCas(&lbFreePtrs[lbHash][i], p, (void*)nullptr) != (void*)nullptr) {
  416. // block was freed while we were busy
  417. AtomicAdd(lbFreePageCount, -realPageCount);
  418. LargeBlockUnmap(p, realPageCount);
  419. --i;
  420. }
  421. }
  422. }
  423. }
  424. char* pRes = AllocWithMMap((pgCount + 1) * 4096ll, MM_HUGE);
  425. #endif
  426. pRes += 4096ll;
  427. TLargeBlk::As(pRes)->SetSize(_nSize, pgCount);
  428. TLargeBlk::As(pRes)->Mark(ELarge::Alloc);
  429. return pRes;
  430. }
  431. #ifndef _MSC_VER
  432. static void FreeAllLargeBlockMem() {
  433. for (auto& lbFreePtr : lbFreePtrs) {
  434. for (int i = 0; i < LB_BUF_SIZE; ++i) {
  435. void* p = lbFreePtr[i];
  436. if (p == nullptr)
  437. continue;
  438. if (DoCas(&lbFreePtr[i], (void*)nullptr, p) == p) {
  439. int pgCount = TLargeBlk::As(p)->Pages;
  440. AtomicAdd(lbFreePageCount, -pgCount);
  441. LargeBlockUnmap(p, pgCount);
  442. }
  443. }
  444. }
  445. }
  446. #endif
  447. static void LargeBlockFree(void* p, ELFAllocCounter counter) {
  448. if (p == nullptr)
  449. return;
  450. #ifdef _MSC_VER
  451. VirtualFree((char*)p - 4096ll, 0, MEM_RELEASE);
  452. #else
  453. size_t pgCount = TLargeBlk::As(p)->Pages;
  454. TLargeBlk::As(p)->Mark(ELarge::Free);
  455. IncrementCounter(counter, pgCount * 4096ll);
  456. IncrementCounter(CT_SYSTEM_FREE, 4096ll);
  457. if (lbFreePageCount > LB_LIMIT_TOTAL_SIZE)
  458. FreeAllLargeBlockMem();
  459. int lbHash = pgCount % LB_BUF_HASH;
  460. for (int i = 0; i < LB_BUF_SIZE; ++i) {
  461. if (lbFreePtrs[lbHash][i] == nullptr) {
  462. if (DoCas(&lbFreePtrs[lbHash][i], p, (void*)nullptr) == nullptr) {
  463. AtomicAdd(lbFreePageCount, pgCount);
  464. return;
  465. }
  466. }
  467. }
  468. LargeBlockUnmap(p, pgCount);
  469. #endif
  470. }
  471. static void* SystemAlloc(size_t _nSize) {
  472. //HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, _nSize);
  473. return LargeBlockAlloc(_nSize, CT_SYSTEM_ALLOC);
  474. }
  475. static void SystemFree(void* p) {
  476. //HeapFree(GetProcessHeap(), 0, p);
  477. LargeBlockFree(p, CT_SYSTEM_FREE);
  478. }
  479. //////////////////////////////////////////////////////////////////////////
  480. char* const LF_LOCK_FREE = ((char*)0) + 0;
  481. char* const LF_LOCK_LOCKED = ((char*)0) + 1;
  482. char* const LF_LOCK_FUTEX_WAIT = ((char*)0) + 2;
  483. static bool LFHasFutex = true;
  484. static bool LFCheckedWinVersion = false;
  485. // TLFLockData has to be zero-initialized explicitly https://en.cppreference.com/w/cpp/language/zero_initialization
  486. // otherwise constructor TLFLockData() for global var might be called after first use
  487. struct TLFLockData
  488. {
  489. char* Pad1[15];
  490. char* volatile LockVar; // = LF_LOCK_FREE; // no constructor, zero-initialize manually
  491. char* Pad2[15];
  492. bool TryLock()
  493. {
  494. return (LockVar == LF_LOCK_FREE && DoCas(&LockVar, LF_LOCK_LOCKED, LF_LOCK_FREE) == LF_LOCK_FREE);
  495. }
  496. void FutexWait()
  497. {
  498. #ifdef _win_
  499. if (!LFCheckedWinVersion) {
  500. OSVERSIONINFOA ver;
  501. memset(&ver, 0, sizeof(ver));
  502. ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
  503. GetVersionExA(&ver);
  504. LFHasFutex = (ver.dwMajorVersion > 6) || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 2);
  505. LFCheckedWinVersion = true;
  506. }
  507. if (LFHasFutex) {
  508. if (LockVar == LF_LOCK_LOCKED) {
  509. DoCas(&LockVar, LF_LOCK_FUTEX_WAIT, LF_LOCK_LOCKED);
  510. }
  511. if (LockVar == LF_LOCK_FUTEX_WAIT) {
  512. char* lockedValue = LF_LOCK_FUTEX_WAIT;
  513. WaitOnAddress(&LockVar, &lockedValue, sizeof(LockVar), INFINITE);
  514. }
  515. } else {
  516. SwitchToThread();
  517. }
  518. #elif defined(_linux_)
  519. if (LFHasFutex) {
  520. if (LockVar == LF_LOCK_LOCKED) {
  521. DoCas(&LockVar, LF_LOCK_FUTEX_WAIT, LF_LOCK_LOCKED);
  522. }
  523. if (LockVar == LF_LOCK_FUTEX_WAIT) {
  524. // linux allow only int variables checks, here we pretend low bits of LockVar are int
  525. syscall(SYS_futex, &LockVar, FUTEX_WAIT_PRIVATE, *(int*)&LF_LOCK_FUTEX_WAIT, 0, 0, 0);
  526. }
  527. } else {
  528. sched_yield();
  529. }
  530. #else
  531. sched_yield();
  532. #endif
  533. }
  534. void Unlock()
  535. {
  536. Y_ASSERT_NOBT(LockVar != LF_LOCK_FREE);
  537. if (DoCas(&LockVar, LF_LOCK_FREE, LF_LOCK_LOCKED) != LF_LOCK_LOCKED) {
  538. Y_ASSERT_NOBT(LockVar == LF_LOCK_FUTEX_WAIT && LFHasFutex);
  539. LockVar = LF_LOCK_FREE;
  540. #ifdef _win_
  541. WakeByAddressAll((PVOID)&LockVar);
  542. #elif defined(_linux_)
  543. syscall(SYS_futex, &LockVar, FUTEX_WAKE_PRIVATE, INT_MAX, 0, 0, 0);
  544. #endif
  545. }
  546. }
  547. };
  548. static TLFLockData LFGlobalLock;
  549. class TLFLockHolder {
  550. TLFLockData *LockData = nullptr;
  551. int Attempt = 0;
  552. int SleepMask = 0x7f;
  553. public:
  554. TLFLockHolder() {}
  555. TLFLockHolder(TLFLockData *lk) {
  556. while (!TryLock(lk));
  557. }
  558. bool TryLock(TLFLockData *lk)
  559. {
  560. Y_ASSERT_NOBT(LockData == nullptr);
  561. if (lk->TryLock()) {
  562. LockData = lk;
  563. return true;
  564. }
  565. if ((++Attempt & SleepMask) == 0) {
  566. lk->FutexWait();
  567. SleepMask = (SleepMask * 2 + 1) & 0x7fff;
  568. } else {
  569. #ifdef _MSC_VER
  570. _mm_pause();
  571. #elif defined(__i386) || defined(__x86_64__)
  572. __asm__ __volatile__("pause");
  573. #endif
  574. }
  575. return false;
  576. }
  577. ~TLFLockHolder() {
  578. if (LockData) {
  579. LockData->Unlock();
  580. }
  581. }
  582. };
  583. //////////////////////////////////////////////////////////////////////////
  584. class TLFAllocFreeList {
  585. struct TNode {
  586. TNode* Next;
  587. };
  588. TNode* volatile Head;
  589. TNode* volatile Pending;
  590. TAtomic PendingToFreeListCounter;
  591. TAtomic AllocCount;
  592. void* Padding;
  593. static Y_FORCE_INLINE void Enqueue(TNode* volatile* headPtr, TNode* n) {
  594. for (;;) {
  595. TNode* volatile prevHead = *headPtr;
  596. n->Next = prevHead;
  597. if (DoCas(headPtr, n, prevHead) == prevHead)
  598. break;
  599. }
  600. }
  601. Y_FORCE_INLINE void* DoAlloc() {
  602. TNode* res;
  603. for (res = Head; res; res = Head) {
  604. TNode* keepNext = res->Next;
  605. if (DoCas(&Head, keepNext, res) == res) {
  606. //Y_ABORT_UNLESS(keepNext == res->Next);
  607. break;
  608. }
  609. }
  610. return res;
  611. }
  612. void FreeList(TNode* fl) {
  613. if (!fl)
  614. return;
  615. TNode* flTail = fl;
  616. while (flTail->Next)
  617. flTail = flTail->Next;
  618. for (;;) {
  619. TNode* volatile prevHead = Head;
  620. flTail->Next = prevHead;
  621. if (DoCas(&Head, fl, prevHead) == prevHead)
  622. break;
  623. }
  624. }
  625. public:
  626. Y_FORCE_INLINE void Free(void* ptr) {
  627. TNode* newFree = (TNode*)ptr;
  628. if (AtomicAdd(AllocCount, 0) == 0)
  629. Enqueue(&Head, newFree);
  630. else
  631. Enqueue(&Pending, newFree);
  632. }
  633. Y_FORCE_INLINE void* Alloc() {
  634. TAtomic keepCounter = AtomicAdd(PendingToFreeListCounter, 0);
  635. TNode* fl = Pending;
  636. if (AtomicAdd(AllocCount, 1) == 1) {
  637. // No other allocs in progress.
  638. // If (keepCounter == PendingToFreeListCounter) then Pending was not freed by other threads.
  639. // Hence Pending is not used in any concurrent DoAlloc() atm and can be safely moved to FreeList
  640. if (fl && keepCounter == AtomicAdd(PendingToFreeListCounter, 0) && DoCas(&Pending, (TNode*)nullptr, fl) == fl) {
  641. // pick first element from Pending and return it
  642. void* res = fl;
  643. fl = fl->Next;
  644. // if there are other elements in Pending list, add them to main free list
  645. FreeList(fl);
  646. AtomicAdd(PendingToFreeListCounter, 1);
  647. AtomicAdd(AllocCount, -1);
  648. return res;
  649. }
  650. }
  651. void* res = DoAlloc();
  652. AtomicAdd(AllocCount, -1);
  653. return res;
  654. }
  655. void* GetWholeList() {
  656. TNode* res;
  657. for (res = Head; res; res = Head) {
  658. if (DoCas(&Head, (TNode*)nullptr, res) == res)
  659. break;
  660. }
  661. return res;
  662. }
  663. void ReturnWholeList(void* ptr) {
  664. while (AtomicAdd(AllocCount, 0) != 0) // theoretically can run into problems with parallel DoAlloc()
  665. ; //ThreadYield();
  666. for (;;) {
  667. TNode* prevHead = Head;
  668. if (DoCas(&Head, (TNode*)ptr, prevHead) == prevHead) {
  669. FreeList(prevHead);
  670. break;
  671. }
  672. }
  673. }
  674. };
  675. /////////////////////////////////////////////////////////////////////////
  676. static TLFAllocFreeList globalFreeLists[N_SIZES];
  677. static char* volatile globalCurrentPtr[N_SIZES];
  678. static TLFAllocFreeList blockFreeList;
  679. // globalFreeLists[] contains TFreeListGroup, each of them points up to 15 free blocks
  680. const int FL_GROUP_SIZE = 15;
  681. struct TFreeListGroup {
  682. TFreeListGroup* Next;
  683. char* Ptrs[FL_GROUP_SIZE];
  684. };
  685. #ifdef _64_
  686. const int FREE_LIST_GROUP_SIZEIDX = 8;
  687. #else
  688. const int FREE_LIST_GROUP_SIZEIDX = 6;
  689. #endif
  690. //////////////////////////////////////////////////////////////////////////
  691. // find free chunks and reset chunk size so they can be reused by different sized allocations
  692. // do not look at blockFreeList (TFreeListGroup has same size for any allocations)
  693. static bool DefragmentMem() {
  694. if (!EnableDefrag) {
  695. return false;
  696. }
  697. IncrementCounter(CT_DEGRAGMENT_CNT, 1);
  698. int* nFreeCount = (int*)SystemAlloc(N_CHUNKS * sizeof(int));
  699. if (Y_UNLIKELY(!nFreeCount)) {
  700. //__debugbreak();
  701. NMalloc::AbortFromCorruptedAllocator("debugbreak");
  702. }
  703. memset(nFreeCount, 0, N_CHUNKS * sizeof(int));
  704. TFreeListGroup* wholeLists[N_SIZES];
  705. for (int nSizeIdx = 0; nSizeIdx < N_SIZES; ++nSizeIdx) {
  706. wholeLists[nSizeIdx] = (TFreeListGroup*)globalFreeLists[nSizeIdx].GetWholeList();
  707. for (TFreeListGroup* g = wholeLists[nSizeIdx]; g; g = g->Next) {
  708. for (auto pData : g->Ptrs) {
  709. if (pData) {
  710. uintptr_t nChunk = (pData - ALLOC_START) / N_CHUNK_SIZE;
  711. ++nFreeCount[nChunk];
  712. Y_ASSERT_NOBT(chunkSizeIdx[nChunk] == nSizeIdx);
  713. }
  714. }
  715. }
  716. }
  717. bool bRes = false;
  718. for (size_t nChunk = 0; nChunk < N_CHUNKS; ++nChunk) {
  719. int fc = nFreeCount[nChunk];
  720. nFreeCount[nChunk] = 0;
  721. if (chunkSizeIdx[nChunk] <= 0)
  722. continue;
  723. int nEntries = N_CHUNK_SIZE / nSizeIdxToSize[static_cast<int>(chunkSizeIdx[nChunk])];
  724. Y_ASSERT_NOBT(fc <= nEntries); // can not have more free blocks then total count
  725. if (fc == nEntries) {
  726. bRes = true;
  727. nFreeCount[nChunk] = 1;
  728. }
  729. }
  730. if (bRes) {
  731. for (auto& wholeList : wholeLists) {
  732. TFreeListGroup** ppPtr = &wholeList;
  733. while (*ppPtr) {
  734. TFreeListGroup* g = *ppPtr;
  735. int dst = 0;
  736. for (auto pData : g->Ptrs) {
  737. if (pData) {
  738. uintptr_t nChunk = (pData - ALLOC_START) / N_CHUNK_SIZE;
  739. if (nFreeCount[nChunk] == 0)
  740. g->Ptrs[dst++] = pData; // block is not freed, keep pointer
  741. }
  742. }
  743. if (dst == 0) {
  744. // no valid pointers in group, free it
  745. *ppPtr = g->Next;
  746. blockFreeList.Free(g);
  747. } else {
  748. // reset invalid pointers to 0
  749. for (int i = dst; i < FL_GROUP_SIZE; ++i)
  750. g->Ptrs[i] = nullptr;
  751. ppPtr = &g->Next;
  752. }
  753. }
  754. }
  755. for (uintptr_t nChunk = 0; nChunk < N_CHUNKS; ++nChunk) {
  756. if (!nFreeCount[nChunk])
  757. continue;
  758. char* pStart = ALLOC_START + nChunk * N_CHUNK_SIZE;
  759. #ifdef _win_
  760. VirtualFree(pStart, N_CHUNK_SIZE, MEM_DECOMMIT);
  761. #elif defined(_freebsd_)
  762. madvise(pStart, N_CHUNK_SIZE, MADV_FREE);
  763. #else
  764. madvise(pStart, N_CHUNK_SIZE, MADV_DONTNEED);
  765. #endif
  766. AddFreeChunk(nChunk);
  767. }
  768. }
  769. for (int nSizeIdx = 0; nSizeIdx < N_SIZES; ++nSizeIdx)
  770. globalFreeLists[nSizeIdx].ReturnWholeList(wholeLists[nSizeIdx]);
  771. SystemFree(nFreeCount);
  772. return bRes;
  773. }
  774. static Y_FORCE_INLINE void* LFAllocFromCurrentChunk(int nSizeIdx, int blockSize, int count) {
  775. char* volatile* pFreeArray = &globalCurrentPtr[nSizeIdx];
  776. while (char* newBlock = *pFreeArray) {
  777. char* nextFree = newBlock + blockSize * count;
  778. // check if there is space in chunk
  779. char* globalEndPtr = ALLOC_START + ((newBlock - ALLOC_START) & ~((uintptr_t)N_CHUNK_SIZE - 1)) + N_CHUNK_SIZE;
  780. if (nextFree >= globalEndPtr) {
  781. if (nextFree > globalEndPtr)
  782. break;
  783. nextFree = nullptr; // it was last block in chunk
  784. }
  785. if (DoCas(pFreeArray, nextFree, newBlock) == newBlock)
  786. return newBlock;
  787. }
  788. return nullptr;
  789. }
  790. enum EDefrag {
  791. MEM_DEFRAG,
  792. NO_MEM_DEFRAG,
  793. };
  794. static void* SlowLFAlloc(int nSizeIdx, int blockSize, EDefrag defrag) {
  795. IncrementCounter(CT_SLOW_ALLOC_CNT, 1);
  796. TLFLockHolder ls;
  797. for (;;) {
  798. bool locked = ls.TryLock(&LFGlobalLock);
  799. void* res = LFAllocFromCurrentChunk(nSizeIdx, blockSize, 1);
  800. if (res) {
  801. return res; // might happen when other thread allocated new current chunk
  802. }
  803. if (locked) {
  804. break;
  805. }
  806. }
  807. for (;;) {
  808. uintptr_t nChunk;
  809. if (GetFreeChunk(&nChunk)) {
  810. char* newPlace = ALLOC_START + nChunk * N_CHUNK_SIZE;
  811. #ifdef _MSC_VER
  812. void* pTest = VirtualAlloc(newPlace, N_CHUNK_SIZE, MEM_COMMIT, PAGE_READWRITE);
  813. Y_ASSERT_NOBT(pTest == newPlace);
  814. #endif
  815. chunkSizeIdx[nChunk] = (char)nSizeIdx;
  816. globalCurrentPtr[nSizeIdx] = newPlace + blockSize;
  817. return newPlace;
  818. }
  819. // out of luck, try to defrag
  820. if (defrag == MEM_DEFRAG && DefragmentMem()) {
  821. continue;
  822. }
  823. char* largeBlock = AllocWithMMap(N_LARGE_ALLOC_SIZE, MM_NORMAL);
  824. uintptr_t addr = ((largeBlock - ALLOC_START) + N_CHUNK_SIZE - 1) & (~(N_CHUNK_SIZE - 1));
  825. uintptr_t endAddr = ((largeBlock - ALLOC_START) + N_LARGE_ALLOC_SIZE) & (~(N_CHUNK_SIZE - 1));
  826. for (uintptr_t p = addr; p < endAddr; p += N_CHUNK_SIZE) {
  827. uintptr_t chunk = p / N_CHUNK_SIZE;
  828. Y_ASSERT_NOBT(chunk * N_CHUNK_SIZE == p);
  829. Y_ASSERT_NOBT(chunkSizeIdx[chunk] == 0);
  830. AddFreeChunk(chunk);
  831. }
  832. }
  833. return nullptr;
  834. }
  835. // allocate single block
  836. static Y_FORCE_INLINE void* LFAllocNoCache(int nSizeIdx, EDefrag defrag) {
  837. int blockSize = nSizeIdxToSize[nSizeIdx];
  838. void* res = LFAllocFromCurrentChunk(nSizeIdx, blockSize, 1);
  839. if (res)
  840. return res;
  841. return SlowLFAlloc(nSizeIdx, blockSize, defrag);
  842. }
  843. // allocate multiple blocks, returns number of blocks allocated (max FL_GROUP_SIZE)
  844. // buf should have space for at least FL_GROUP_SIZE elems
  845. static Y_FORCE_INLINE int LFAllocNoCacheMultiple(int nSizeIdx, char** buf) {
  846. int blockSize = nSizeIdxToSize[nSizeIdx];
  847. void* res = LFAllocFromCurrentChunk(nSizeIdx, blockSize, FL_GROUP_SIZE);
  848. if (res) {
  849. char* resPtr = (char*)res;
  850. for (int k = 0; k < FL_GROUP_SIZE; ++k) {
  851. buf[k] = resPtr;
  852. resPtr += blockSize;
  853. }
  854. return FL_GROUP_SIZE;
  855. }
  856. buf[0] = (char*)SlowLFAlloc(nSizeIdx, blockSize, MEM_DEFRAG);
  857. return 1;
  858. }
  859. // take several blocks from global free list (max FL_GROUP_SIZE blocks), returns number of blocks taken
  860. // buf should have space for at least FL_GROUP_SIZE elems
  861. static Y_FORCE_INLINE int TakeBlocksFromGlobalFreeList(int nSizeIdx, char** buf) {
  862. TLFAllocFreeList& fl = globalFreeLists[nSizeIdx];
  863. TFreeListGroup* g = (TFreeListGroup*)fl.Alloc();
  864. if (g) {
  865. int resCount = 0;
  866. for (auto& ptr : g->Ptrs) {
  867. if (ptr)
  868. buf[resCount++] = ptr;
  869. else
  870. break;
  871. }
  872. blockFreeList.Free(g);
  873. return resCount;
  874. }
  875. return 0;
  876. }
  877. // add several blocks to global free list
  878. static Y_FORCE_INLINE void PutBlocksToGlobalFreeList(ptrdiff_t nSizeIdx, char** buf, int count) {
  879. for (int startIdx = 0; startIdx < count;) {
  880. TFreeListGroup* g = (TFreeListGroup*)blockFreeList.Alloc();
  881. Y_ASSERT_NOBT(sizeof(TFreeListGroup) == nSizeIdxToSize[FREE_LIST_GROUP_SIZEIDX]);
  882. if (!g) {
  883. g = (TFreeListGroup*)LFAllocNoCache(FREE_LIST_GROUP_SIZEIDX, NO_MEM_DEFRAG);
  884. }
  885. int groupSize = count - startIdx;
  886. if (groupSize > FL_GROUP_SIZE)
  887. groupSize = FL_GROUP_SIZE;
  888. for (int i = 0; i < groupSize; ++i)
  889. g->Ptrs[i] = buf[startIdx + i];
  890. for (int i = groupSize; i < FL_GROUP_SIZE; ++i)
  891. g->Ptrs[i] = nullptr;
  892. // add free group to the global list
  893. TLFAllocFreeList& fl = globalFreeLists[nSizeIdx];
  894. fl.Free(g);
  895. startIdx += groupSize;
  896. }
  897. }
  898. //////////////////////////////////////////////////////////////////////////
  899. static TAtomic GlobalCounters[CT_MAX];
  900. const int MAX_LOCAL_UPDATES = 100;
  901. const intptr_t MAX_LOCAL_DELTA = 1*1024*1024;
  902. struct TLocalCounter {
  903. intptr_t Value;
  904. int Updates;
  905. TAtomic* Parent;
  906. Y_FORCE_INLINE void Init(TAtomic* parent) {
  907. Parent = parent;
  908. Value = 0;
  909. Updates = 0;
  910. }
  911. Y_FORCE_INLINE void Increment(size_t value) {
  912. Value += value;
  913. if (++Updates > MAX_LOCAL_UPDATES || Value > MAX_LOCAL_DELTA) {
  914. Flush();
  915. }
  916. }
  917. Y_FORCE_INLINE void Flush() {
  918. AtomicAdd(*Parent, Value);
  919. Value = 0;
  920. Updates = 0;
  921. }
  922. };
  923. ////////////////////////////////////////////////////////////////////////////////
  924. // DBG stuff
  925. ////////////////////////////////////////////////////////////////////////////////
  926. #if defined(LFALLOC_DBG)
  927. struct TPerTagAllocCounter {
  928. TAtomic Size;
  929. TAtomic Count;
  930. Y_FORCE_INLINE void Alloc(size_t size) {
  931. AtomicAdd(Size, size);
  932. AtomicAdd(Count, 1);
  933. }
  934. Y_FORCE_INLINE void Free(size_t size) {
  935. AtomicSub(Size, size);
  936. AtomicSub(Count, 1);
  937. }
  938. };
  939. struct TLocalPerTagAllocCounter {
  940. intptr_t Size;
  941. int Count;
  942. int Updates;
  943. Y_FORCE_INLINE void Init() {
  944. Size = 0;
  945. Count = 0;
  946. Updates = 0;
  947. }
  948. Y_FORCE_INLINE void Alloc(TPerTagAllocCounter& parent, size_t size) {
  949. Size += size;
  950. ++Count;
  951. if (++Updates > MAX_LOCAL_UPDATES) {
  952. Flush(parent);
  953. }
  954. }
  955. Y_FORCE_INLINE void Free(TPerTagAllocCounter& parent, size_t size) {
  956. Size -= size;
  957. --Count;
  958. if (++Updates > MAX_LOCAL_UPDATES) {
  959. Flush(parent);
  960. }
  961. }
  962. Y_FORCE_INLINE void Flush(TPerTagAllocCounter& parent) {
  963. AtomicAdd(parent.Size, Size);
  964. Size = 0;
  965. AtomicAdd(parent.Count, Count);
  966. Count = 0;
  967. Updates = 0;
  968. }
  969. };
  970. static const int DBG_ALLOC_MAX_TAG = 1000;
  971. static const int DBG_ALLOC_ALIGNED_TAG = 0xF0000000;
  972. static const int DBG_ALLOC_NUM_SIZES = 30;
  973. static TPerTagAllocCounter GlobalPerTagAllocCounters[DBG_ALLOC_MAX_TAG][DBG_ALLOC_NUM_SIZES];
  974. #endif // LFALLOC_DBG
  975. //////////////////////////////////////////////////////////////////////////
  976. const int THREAD_BUF = 256;
  977. static int borderSizes[N_SIZES];
  978. const int MAX_MEM_PER_SIZE_PER_THREAD = 512 * 1024;
  979. struct TThreadAllocInfo {
  980. // FreePtrs - pointers to first free blocks in per thread block list
  981. // LastFreePtrs - pointers to last blocks in lists, may be invalid if FreePtr is zero
  982. char* FreePtrs[N_SIZES][THREAD_BUF];
  983. int FreePtrIndex[N_SIZES];
  984. TThreadAllocInfo* pNextInfo;
  985. TLocalCounter LocalCounters[CT_MAX];
  986. #if defined(LFALLOC_DBG)
  987. TLocalPerTagAllocCounter LocalPerTagAllocCounters[DBG_ALLOC_MAX_TAG][DBG_ALLOC_NUM_SIZES];
  988. #endif
  989. #ifdef _win_
  990. HANDLE hThread;
  991. #endif
  992. void Init(TThreadAllocInfo** pHead) {
  993. memset(this, 0, sizeof(*this));
  994. for (auto& i : FreePtrIndex)
  995. i = THREAD_BUF;
  996. #ifdef _win_
  997. BOOL b = DuplicateHandle(
  998. GetCurrentProcess(), GetCurrentThread(),
  999. GetCurrentProcess(), &hThread,
  1000. 0, FALSE, DUPLICATE_SAME_ACCESS);
  1001. Y_ASSERT_NOBT(b);
  1002. #endif
  1003. pNextInfo = *pHead;
  1004. *pHead = this;
  1005. for (int k = 0; k < N_SIZES; ++k) {
  1006. int maxCount = MAX_MEM_PER_SIZE_PER_THREAD / nSizeIdxToSize[k];
  1007. if (maxCount > THREAD_BUF)
  1008. maxCount = THREAD_BUF;
  1009. borderSizes[k] = THREAD_BUF - maxCount;
  1010. }
  1011. for (int i = 0; i < CT_MAX; ++i) {
  1012. LocalCounters[i].Init(&GlobalCounters[i]);
  1013. }
  1014. #if defined(LFALLOC_DBG)
  1015. for (int tag = 0; tag < DBG_ALLOC_MAX_TAG; ++tag) {
  1016. for (int sizeIdx = 0; sizeIdx < DBG_ALLOC_NUM_SIZES; ++sizeIdx) {
  1017. auto& local = LocalPerTagAllocCounters[tag][sizeIdx];
  1018. local.Init();
  1019. }
  1020. }
  1021. #endif
  1022. }
  1023. void Done() {
  1024. for (auto sizeIdx : FreePtrIndex) {
  1025. Y_ASSERT_NOBT(sizeIdx == THREAD_BUF);
  1026. }
  1027. for (auto& localCounter : LocalCounters) {
  1028. localCounter.Flush();
  1029. }
  1030. #if defined(LFALLOC_DBG)
  1031. for (int tag = 0; tag < DBG_ALLOC_MAX_TAG; ++tag) {
  1032. for (int sizeIdx = 0; sizeIdx < DBG_ALLOC_NUM_SIZES; ++sizeIdx) {
  1033. auto& local = LocalPerTagAllocCounters[tag][sizeIdx];
  1034. auto& global = GlobalPerTagAllocCounters[tag][sizeIdx];
  1035. local.Flush(global);
  1036. }
  1037. }
  1038. #endif
  1039. #ifdef _win_
  1040. if (hThread)
  1041. CloseHandle(hThread);
  1042. #endif
  1043. }
  1044. };
  1045. PERTHREAD TThreadAllocInfo* pThreadInfo;
  1046. static TThreadAllocInfo* pThreadInfoList;
  1047. static TLFLockData LFLockThreadInfo;
  1048. static Y_FORCE_INLINE void IncrementCounter(ELFAllocCounter counter, size_t value) {
  1049. #ifdef LFALLOC_YT
  1050. TThreadAllocInfo* thr = pThreadInfo;
  1051. if (thr) {
  1052. thr->LocalCounters[counter].Increment(value);
  1053. } else {
  1054. AtomicAdd(GlobalCounters[counter], value);
  1055. }
  1056. #endif
  1057. }
  1058. extern "C" i64 GetLFAllocCounterFast(int counter) {
  1059. #ifdef LFALLOC_YT
  1060. return GlobalCounters[counter];
  1061. #else
  1062. return 0;
  1063. #endif
  1064. }
  1065. extern "C" i64 GetLFAllocCounterFull(int counter) {
  1066. #ifdef LFALLOC_YT
  1067. i64 ret = GlobalCounters[counter];
  1068. {
  1069. TLFLockHolder ll(&LFLockThreadInfo);
  1070. for (TThreadAllocInfo** p = &pThreadInfoList; *p;) {
  1071. TThreadAllocInfo* pInfo = *p;
  1072. ret += pInfo->LocalCounters[counter].Value;
  1073. p = &pInfo->pNextInfo;
  1074. }
  1075. }
  1076. return ret;
  1077. #else
  1078. return 0;
  1079. #endif
  1080. }
  1081. static void MoveSingleThreadFreeToGlobal(TThreadAllocInfo* pInfo) {
  1082. for (int sizeIdx = 0; sizeIdx < N_SIZES; ++sizeIdx) {
  1083. int& freePtrIdx = pInfo->FreePtrIndex[sizeIdx];
  1084. char** freePtrs = pInfo->FreePtrs[sizeIdx];
  1085. PutBlocksToGlobalFreeList(sizeIdx, freePtrs + freePtrIdx, THREAD_BUF - freePtrIdx);
  1086. freePtrIdx = THREAD_BUF;
  1087. }
  1088. }
  1089. #ifdef _win_
  1090. static bool IsDeadThread(TThreadAllocInfo* pInfo) {
  1091. DWORD dwExit;
  1092. bool isDead = !GetExitCodeThread(pInfo->hThread, &dwExit) || dwExit != STILL_ACTIVE;
  1093. return isDead;
  1094. }
  1095. static void CleanupAfterDeadThreads() {
  1096. TLFLockHolder ll(&LFLockThreadInfo);
  1097. for (TThreadAllocInfo** p = &pThreadInfoList; *p;) {
  1098. TThreadAllocInfo* pInfo = *p;
  1099. if (IsDeadThread(pInfo)) {
  1100. MoveSingleThreadFreeToGlobal(pInfo);
  1101. pInfo->Done();
  1102. *p = pInfo->pNextInfo;
  1103. SystemFree(pInfo);
  1104. } else
  1105. p = &pInfo->pNextInfo;
  1106. }
  1107. }
  1108. #endif
  1109. #ifndef _win_
  1110. static pthread_key_t ThreadCacheCleaner;
  1111. static void* volatile ThreadCacheCleanerStarted; // 0 = not started, -1 = started, -2 = is starting
  1112. static PERTHREAD bool IsStoppingThread;
  1113. static void FreeThreadCache(void*) {
  1114. TThreadAllocInfo* pToDelete = nullptr;
  1115. {
  1116. TLFLockHolder ll(&LFLockThreadInfo);
  1117. pToDelete = pThreadInfo;
  1118. if (pToDelete == nullptr)
  1119. return;
  1120. // remove from the list
  1121. for (TThreadAllocInfo** p = &pThreadInfoList; *p; p = &(*p)->pNextInfo) {
  1122. if (*p == pToDelete) {
  1123. *p = pToDelete->pNextInfo;
  1124. break;
  1125. }
  1126. }
  1127. IsStoppingThread = true;
  1128. pThreadInfo = nullptr;
  1129. }
  1130. // free per thread buf
  1131. MoveSingleThreadFreeToGlobal(pToDelete);
  1132. pToDelete->Done();
  1133. SystemFree(pToDelete);
  1134. }
  1135. #endif
  1136. static void AllocThreadInfo() {
  1137. #ifndef _win_
  1138. if (DoCas(&ThreadCacheCleanerStarted, (void*)-2, (void*)nullptr) == (void*)nullptr) {
  1139. pthread_key_create(&ThreadCacheCleaner, FreeThreadCache);
  1140. ThreadCacheCleanerStarted = (void*)-1;
  1141. }
  1142. if (ThreadCacheCleanerStarted != (void*)-1)
  1143. return; // do not use ThreadCacheCleaner until it is constructed
  1144. {
  1145. if (IsStoppingThread)
  1146. return;
  1147. TLFLockHolder ll(&LFLockThreadInfo);
  1148. if (IsStoppingThread) // better safe than sorry
  1149. return;
  1150. pThreadInfo = (TThreadAllocInfo*)SystemAlloc(sizeof(TThreadAllocInfo));
  1151. pThreadInfo->Init(&pThreadInfoList);
  1152. }
  1153. pthread_setspecific(ThreadCacheCleaner, (void*)-1); // without value destructor will not be called
  1154. #else
  1155. CleanupAfterDeadThreads();
  1156. {
  1157. pThreadInfo = (TThreadAllocInfo*)SystemAlloc(sizeof(TThreadAllocInfo));
  1158. TLFLockHolder ll(&LFLockThreadInfo);
  1159. pThreadInfo->Init(&pThreadInfoList);
  1160. }
  1161. #endif
  1162. }
  1163. //////////////////////////////////////////////////////////////////////////
  1164. // DBG stuff
  1165. //////////////////////////////////////////////////////////////////////////
  1166. #if defined(LFALLOC_DBG)
  1167. struct TAllocHeader {
  1168. uint64_t Size;
  1169. int Tag;
  1170. int Cookie;
  1171. };
  1172. // should be power of 2
  1173. static_assert(sizeof(TAllocHeader) == 16);
  1174. static inline void* GetAllocPtr(TAllocHeader* p) {
  1175. return p + 1;
  1176. }
  1177. static inline TAllocHeader* GetAllocHeader(void* p) {
  1178. return ((TAllocHeader*)p) - 1;
  1179. }
  1180. // if present, uses the fake header stored by LFPosixMemalign() to retrieve the original header.
  1181. static inline TAllocHeader* GetOrigAllocHeader(void* p) {
  1182. auto* header = GetAllocHeader(p);
  1183. if (header->Tag == DBG_ALLOC_ALIGNED_TAG) {
  1184. return (TAllocHeader*)header->Size;
  1185. }
  1186. return header;
  1187. }
  1188. PERTHREAD int AllocationTag;
  1189. extern "C" int SetThreadAllocTag(int tag) {
  1190. int prevTag = AllocationTag;
  1191. if (tag < DBG_ALLOC_MAX_TAG && tag >= 0) {
  1192. AllocationTag = tag;
  1193. }
  1194. return prevTag;
  1195. }
  1196. PERTHREAD bool ProfileCurrentThread;
  1197. extern "C" bool SetProfileCurrentThread(bool newVal) {
  1198. bool prevVal = ProfileCurrentThread;
  1199. ProfileCurrentThread = newVal;
  1200. return prevVal;
  1201. }
  1202. static volatile bool ProfileAllThreads;
  1203. extern "C" bool SetProfileAllThreads(bool newVal) {
  1204. bool prevVal = ProfileAllThreads;
  1205. ProfileAllThreads = newVal;
  1206. return prevVal;
  1207. }
  1208. static volatile bool AllocationSamplingEnabled;
  1209. extern "C" bool SetAllocationSamplingEnabled(bool newVal) {
  1210. bool prevVal = AllocationSamplingEnabled;
  1211. AllocationSamplingEnabled = newVal;
  1212. return prevVal;
  1213. }
  1214. static size_t AllocationSampleRate = 1000;
  1215. extern "C" size_t SetAllocationSampleRate(size_t newVal) {
  1216. size_t prevVal = AllocationSampleRate;
  1217. AllocationSampleRate = newVal;
  1218. return prevVal;
  1219. }
  1220. static size_t AllocationSampleMaxSize = N_MAX_FAST_SIZE;
  1221. extern "C" size_t SetAllocationSampleMaxSize(size_t newVal) {
  1222. size_t prevVal = AllocationSampleMaxSize;
  1223. AllocationSampleMaxSize = newVal;
  1224. return prevVal;
  1225. }
  1226. using TAllocationCallback = int(int tag, size_t size, int sizeIdx);
  1227. static TAllocationCallback* AllocationCallback;
  1228. extern "C" TAllocationCallback* SetAllocationCallback(TAllocationCallback* newVal) {
  1229. TAllocationCallback* prevVal = AllocationCallback;
  1230. AllocationCallback = newVal;
  1231. return prevVal;
  1232. }
  1233. using TDeallocationCallback = void(int cookie, int tag, size_t size, int sizeIdx);
  1234. static TDeallocationCallback* DeallocationCallback;
  1235. extern "C" TDeallocationCallback* SetDeallocationCallback(TDeallocationCallback* newVal) {
  1236. TDeallocationCallback* prevVal = DeallocationCallback;
  1237. DeallocationCallback = newVal;
  1238. return prevVal;
  1239. }
  1240. PERTHREAD TAtomic AllocationsCount;
  1241. PERTHREAD bool InAllocationCallback;
  1242. static const int DBG_ALLOC_INVALID_COOKIE = -1;
  1243. static inline int SampleAllocation(TAllocHeader* p, int sizeIdx) {
  1244. int cookie = DBG_ALLOC_INVALID_COOKIE;
  1245. if (AllocationSamplingEnabled && (ProfileCurrentThread || ProfileAllThreads) && !InAllocationCallback) {
  1246. if (p->Size > AllocationSampleMaxSize || ++AllocationsCount % AllocationSampleRate == 0) {
  1247. if (AllocationCallback) {
  1248. InAllocationCallback = true;
  1249. cookie = AllocationCallback(p->Tag, p->Size, sizeIdx);
  1250. InAllocationCallback = false;
  1251. }
  1252. }
  1253. }
  1254. return cookie;
  1255. }
  1256. static inline void SampleDeallocation(TAllocHeader* p, int sizeIdx) {
  1257. if (p->Cookie != DBG_ALLOC_INVALID_COOKIE && !InAllocationCallback) {
  1258. if (DeallocationCallback) {
  1259. InAllocationCallback = true;
  1260. DeallocationCallback(p->Cookie, p->Tag, p->Size, sizeIdx);
  1261. InAllocationCallback = false;
  1262. }
  1263. }
  1264. }
  1265. static inline void TrackPerTagAllocation(TAllocHeader* p, int sizeIdx) {
  1266. if (p->Tag < DBG_ALLOC_MAX_TAG && p->Tag >= 0) {
  1267. Y_ASSERT_NOBT(sizeIdx < DBG_ALLOC_NUM_SIZES);
  1268. auto& global = GlobalPerTagAllocCounters[p->Tag][sizeIdx];
  1269. TThreadAllocInfo* thr = pThreadInfo;
  1270. if (thr) {
  1271. auto& local = thr->LocalPerTagAllocCounters[p->Tag][sizeIdx];
  1272. local.Alloc(global, p->Size);
  1273. } else {
  1274. global.Alloc(p->Size);
  1275. }
  1276. }
  1277. }
  1278. static inline void TrackPerTagDeallocation(TAllocHeader* p, int sizeIdx) {
  1279. if (p->Tag < DBG_ALLOC_MAX_TAG && p->Tag >= 0) {
  1280. Y_ASSERT_NOBT(sizeIdx < DBG_ALLOC_NUM_SIZES);
  1281. auto& global = GlobalPerTagAllocCounters[p->Tag][sizeIdx];
  1282. TThreadAllocInfo* thr = pThreadInfo;
  1283. if (thr) {
  1284. auto& local = thr->LocalPerTagAllocCounters[p->Tag][sizeIdx];
  1285. local.Free(global, p->Size);
  1286. } else {
  1287. global.Free(p->Size);
  1288. }
  1289. }
  1290. }
  1291. static void* TrackAllocation(void* ptr, size_t size, int sizeIdx) {
  1292. TAllocHeader* p = (TAllocHeader*)ptr;
  1293. p->Size = size;
  1294. p->Tag = AllocationTag;
  1295. p->Cookie = SampleAllocation(p, sizeIdx);
  1296. TrackPerTagAllocation(p, sizeIdx);
  1297. return GetAllocPtr(p);
  1298. }
  1299. static void TrackDeallocation(void* ptr, int sizeIdx) {
  1300. TAllocHeader* p = (TAllocHeader*)ptr;
  1301. SampleDeallocation(p, sizeIdx);
  1302. TrackPerTagDeallocation(p, sizeIdx);
  1303. }
  1304. struct TPerTagAllocInfo {
  1305. ssize_t Count;
  1306. ssize_t Size;
  1307. };
  1308. extern "C" void GetPerTagAllocInfo(
  1309. bool flushPerThreadCounters,
  1310. TPerTagAllocInfo* info,
  1311. int& maxTag,
  1312. int& numSizes) {
  1313. maxTag = DBG_ALLOC_MAX_TAG;
  1314. numSizes = DBG_ALLOC_NUM_SIZES;
  1315. if (info) {
  1316. if (flushPerThreadCounters) {
  1317. TLFLockHolder ll(&LFLockThreadInfo);
  1318. for (TThreadAllocInfo** p = &pThreadInfoList; *p;) {
  1319. TThreadAllocInfo* pInfo = *p;
  1320. for (int tag = 0; tag < DBG_ALLOC_MAX_TAG; ++tag) {
  1321. for (int sizeIdx = 0; sizeIdx < DBG_ALLOC_NUM_SIZES; ++sizeIdx) {
  1322. auto& local = pInfo->LocalPerTagAllocCounters[tag][sizeIdx];
  1323. auto& global = GlobalPerTagAllocCounters[tag][sizeIdx];
  1324. local.Flush(global);
  1325. }
  1326. }
  1327. p = &pInfo->pNextInfo;
  1328. }
  1329. }
  1330. for (int tag = 0; tag < DBG_ALLOC_MAX_TAG; ++tag) {
  1331. for (int sizeIdx = 0; sizeIdx < DBG_ALLOC_NUM_SIZES; ++sizeIdx) {
  1332. auto& global = GlobalPerTagAllocCounters[tag][sizeIdx];
  1333. auto& res = info[tag * DBG_ALLOC_NUM_SIZES + sizeIdx];
  1334. res.Count = global.Count;
  1335. res.Size = global.Size;
  1336. }
  1337. }
  1338. }
  1339. }
  1340. #endif // LFALLOC_DBG
  1341. //////////////////////////////////////////////////////////////////////////
  1342. static Y_FORCE_INLINE void* LFAllocImpl(size_t _nSize) {
  1343. #if defined(LFALLOC_DBG)
  1344. size_t size = _nSize;
  1345. _nSize += sizeof(TAllocHeader);
  1346. #endif
  1347. IncrementCounter(CT_USER_ALLOC, _nSize);
  1348. int nSizeIdx;
  1349. if (_nSize > 512) {
  1350. if (_nSize > N_MAX_FAST_SIZE) {
  1351. void* ptr = LargeBlockAlloc(_nSize, CT_LARGE_ALLOC);
  1352. #if defined(LFALLOC_DBG)
  1353. ptr = TrackAllocation(ptr, size, N_SIZES);
  1354. #endif
  1355. return ptr;
  1356. }
  1357. nSizeIdx = size2idxArr2[(_nSize - 1) >> 8];
  1358. } else
  1359. nSizeIdx = size2idxArr1[1 + (((int)_nSize - 1) >> 3)];
  1360. IncrementCounter(CT_SMALL_ALLOC, nSizeIdxToSize[nSizeIdx]);
  1361. // check per thread buffer
  1362. TThreadAllocInfo* thr = pThreadInfo;
  1363. if (!thr) {
  1364. AllocThreadInfo();
  1365. thr = pThreadInfo;
  1366. if (!thr) {
  1367. void* ptr = LFAllocNoCache(nSizeIdx, MEM_DEFRAG);
  1368. #if defined(LFALLOC_DBG)
  1369. ptr = TrackAllocation(ptr, size, nSizeIdx);
  1370. #endif
  1371. return ptr;
  1372. }
  1373. }
  1374. {
  1375. int& freePtrIdx = thr->FreePtrIndex[nSizeIdx];
  1376. if (freePtrIdx < THREAD_BUF) {
  1377. void* ptr = thr->FreePtrs[nSizeIdx][freePtrIdx++];
  1378. #if defined(LFALLOC_DBG)
  1379. ptr = TrackAllocation(ptr, size, nSizeIdx);
  1380. #endif
  1381. return ptr;
  1382. }
  1383. // try to alloc from global free list
  1384. char* buf[FL_GROUP_SIZE];
  1385. int count = TakeBlocksFromGlobalFreeList(nSizeIdx, buf);
  1386. if (count == 0) {
  1387. count = LFAllocNoCacheMultiple(nSizeIdx, buf);
  1388. if (count == 0) {
  1389. NMalloc::AbortFromCorruptedAllocator("no way LFAllocNoCacheMultiple() can fail");
  1390. }
  1391. }
  1392. char** dstBuf = thr->FreePtrs[nSizeIdx] + freePtrIdx - 1;
  1393. for (int i = 0; i < count - 1; ++i)
  1394. dstBuf[-i] = buf[i];
  1395. freePtrIdx -= count - 1;
  1396. void* ptr = buf[count - 1];
  1397. #if defined(LFALLOC_DBG)
  1398. ptr = TrackAllocation(ptr, size, nSizeIdx);
  1399. #endif
  1400. return ptr;
  1401. }
  1402. }
  1403. static Y_FORCE_INLINE void* LFAlloc(size_t _nSize) {
  1404. void* res = LFAllocImpl(_nSize);
  1405. #ifdef DBG_FILL_MEMORY
  1406. if (FillMemoryOnAllocation && res && (_nSize <= DBG_FILL_MAX_SIZE)) {
  1407. memset(res, 0xcf, _nSize);
  1408. }
  1409. #endif
  1410. return res;
  1411. }
  1412. static Y_FORCE_INLINE void LFFree(void* p) {
  1413. #if defined(LFALLOC_DBG)
  1414. if (p == nullptr)
  1415. return;
  1416. p = GetOrigAllocHeader(p);
  1417. #endif
  1418. uintptr_t chkOffset = ((char*)p - ALLOC_START) - 1ll;
  1419. if (chkOffset >= N_MAX_WORKSET_SIZE) {
  1420. if (p == nullptr)
  1421. return;
  1422. #if defined(LFALLOC_DBG)
  1423. TrackDeallocation(p, N_SIZES);
  1424. #endif
  1425. LargeBlockFree(p, CT_LARGE_FREE);
  1426. return;
  1427. }
  1428. uintptr_t chunk = ((char*)p - ALLOC_START) / N_CHUNK_SIZE;
  1429. ptrdiff_t nSizeIdx = chunkSizeIdx[chunk];
  1430. if (nSizeIdx <= 0) {
  1431. #if defined(LFALLOC_DBG)
  1432. TrackDeallocation(p, N_SIZES);
  1433. #endif
  1434. LargeBlockFree(p, CT_LARGE_FREE);
  1435. return;
  1436. }
  1437. #if defined(LFALLOC_DBG)
  1438. TrackDeallocation(p, nSizeIdx);
  1439. #endif
  1440. #ifdef DBG_FILL_MEMORY
  1441. memset(p, 0xfe, nSizeIdxToSize[nSizeIdx]);
  1442. #endif
  1443. IncrementCounter(CT_SMALL_FREE, nSizeIdxToSize[nSizeIdx]);
  1444. // try to store info to per thread buf
  1445. TThreadAllocInfo* thr = pThreadInfo;
  1446. if (thr) {
  1447. int& freePtrIdx = thr->FreePtrIndex[nSizeIdx];
  1448. if (freePtrIdx > borderSizes[nSizeIdx]) {
  1449. thr->FreePtrs[nSizeIdx][--freePtrIdx] = (char*)p;
  1450. return;
  1451. }
  1452. // move several pointers to global free list
  1453. int freeCount = FL_GROUP_SIZE;
  1454. if (freeCount > THREAD_BUF - freePtrIdx)
  1455. freeCount = THREAD_BUF - freePtrIdx;
  1456. char** freePtrs = thr->FreePtrs[nSizeIdx];
  1457. PutBlocksToGlobalFreeList(nSizeIdx, freePtrs + freePtrIdx, freeCount);
  1458. freePtrIdx += freeCount;
  1459. freePtrs[--freePtrIdx] = (char*)p;
  1460. } else {
  1461. AllocThreadInfo();
  1462. PutBlocksToGlobalFreeList(nSizeIdx, (char**)&p, 1);
  1463. }
  1464. }
  1465. static size_t LFGetSize(const void* p) {
  1466. #if defined(LFALLOC_DBG)
  1467. if (p == nullptr)
  1468. return 0;
  1469. return GetOrigAllocHeader(const_cast<void*>(p))->Size;
  1470. #endif
  1471. uintptr_t chkOffset = ((const char*)p - ALLOC_START);
  1472. if (chkOffset >= N_MAX_WORKSET_SIZE) {
  1473. if (p == nullptr)
  1474. return 0;
  1475. return TLargeBlk::As(p)->Pages * 4096ll;
  1476. }
  1477. uintptr_t chunk = ((const char*)p - ALLOC_START) / N_CHUNK_SIZE;
  1478. ptrdiff_t nSizeIdx = chunkSizeIdx[chunk];
  1479. if (nSizeIdx <= 0)
  1480. return TLargeBlk::As(p)->Pages * 4096ll;
  1481. return nSizeIdxToSize[nSizeIdx];
  1482. }
  1483. ////////////////////////////////////////////////////////////////////////////////////////////////////
  1484. // Output mem alloc stats
  1485. const int N_PAGE_SIZE = 4096;
  1486. static void DebugTraceMMgr(const char* pszFormat, ...) // __cdecl
  1487. {
  1488. static char buff[20000];
  1489. va_list va;
  1490. //
  1491. va_start(va, pszFormat);
  1492. vsprintf(buff, pszFormat, va);
  1493. va_end(va);
  1494. //
  1495. #ifdef _win_
  1496. OutputDebugStringA(buff);
  1497. #else
  1498. fputs(buff, stderr);
  1499. #endif
  1500. }
  1501. struct TChunkStats {
  1502. char *Start, *Finish;
  1503. i64 Size;
  1504. char* Entries;
  1505. i64 FreeCount;
  1506. TChunkStats(size_t chunk, i64 size, char* entries)
  1507. : Size(size)
  1508. , Entries(entries)
  1509. , FreeCount(0)
  1510. {
  1511. Start = ALLOC_START + chunk * N_CHUNK_SIZE;
  1512. Finish = Start + N_CHUNK_SIZE;
  1513. }
  1514. void CheckBlock(char* pBlock) {
  1515. if (pBlock && pBlock >= Start && pBlock < Finish) {
  1516. ++FreeCount;
  1517. i64 nShift = pBlock - Start;
  1518. i64 nOffsetInStep = nShift & (N_CHUNK_SIZE - 1);
  1519. Entries[nOffsetInStep / Size] = 1;
  1520. }
  1521. }
  1522. void SetGlobalFree(char* ptr) {
  1523. i64 nShift = ptr - Start;
  1524. i64 nOffsetInStep = nShift & (N_CHUNK_SIZE - 1);
  1525. while (nOffsetInStep + Size <= N_CHUNK_SIZE) {
  1526. ++FreeCount;
  1527. Entries[nOffsetInStep / Size] = 1;
  1528. nOffsetInStep += Size;
  1529. }
  1530. }
  1531. };
  1532. static void DumpMemoryBlockUtilizationLocked() {
  1533. TFreeListGroup* wholeLists[N_SIZES];
  1534. for (int nSizeIdx = 0; nSizeIdx < N_SIZES; ++nSizeIdx) {
  1535. wholeLists[nSizeIdx] = (TFreeListGroup*)globalFreeLists[nSizeIdx].GetWholeList();
  1536. }
  1537. char* bfList = (char*)blockFreeList.GetWholeList();
  1538. DebugTraceMMgr("memory blocks utilisation stats:\n");
  1539. i64 nTotalAllocated = 0, nTotalFree = 0, nTotalBadPages = 0, nTotalPages = 0, nTotalUsed = 0, nTotalLocked = 0;
  1540. i64 nTotalGroupBlocks = 0;
  1541. char* entries;
  1542. entries = (char*)SystemAlloc((N_CHUNK_SIZE / 4));
  1543. for (size_t k = 0; k < N_CHUNKS; ++k) {
  1544. if (chunkSizeIdx[k] <= 0) {
  1545. if (chunkSizeIdx[k] == -1)
  1546. nTotalLocked += N_CHUNK_SIZE;
  1547. continue;
  1548. }
  1549. i64 nSizeIdx = chunkSizeIdx[k];
  1550. i64 nSize = nSizeIdxToSize[nSizeIdx];
  1551. TChunkStats cs(k, nSize, entries);
  1552. int nEntriesTotal = N_CHUNK_SIZE / nSize;
  1553. memset(entries, 0, nEntriesTotal);
  1554. for (TFreeListGroup* g = wholeLists[nSizeIdx]; g; g = g->Next) {
  1555. for (auto& ptr : g->Ptrs)
  1556. cs.CheckBlock(ptr);
  1557. }
  1558. TChunkStats csGB(k, nSize, entries);
  1559. if (nSizeIdx == FREE_LIST_GROUP_SIZEIDX) {
  1560. for (auto g : wholeLists) {
  1561. for (; g; g = g->Next)
  1562. csGB.CheckBlock((char*)g);
  1563. }
  1564. for (char* blk = bfList; blk; blk = *(char**)blk)
  1565. csGB.CheckBlock(blk);
  1566. nTotalGroupBlocks += csGB.FreeCount * nSize;
  1567. }
  1568. if (((globalCurrentPtr[nSizeIdx] - ALLOC_START) / N_CHUNK_SIZE) == k)
  1569. cs.SetGlobalFree(globalCurrentPtr[nSizeIdx]);
  1570. nTotalUsed += (nEntriesTotal - cs.FreeCount - csGB.FreeCount) * nSize;
  1571. char pages[N_CHUNK_SIZE / N_PAGE_SIZE];
  1572. memset(pages, 0, sizeof(pages));
  1573. for (int i = 0, nShift = 0; i < nEntriesTotal; ++i, nShift += nSize) {
  1574. int nBit = 0;
  1575. if (entries[i])
  1576. nBit = 1; // free entry
  1577. else
  1578. nBit = 2; // used entry
  1579. for (i64 nDelta = nSize - 1; nDelta >= 0; nDelta -= N_PAGE_SIZE)
  1580. pages[(nShift + nDelta) / N_PAGE_SIZE] |= nBit;
  1581. }
  1582. i64 nBadPages = 0;
  1583. for (auto page : pages) {
  1584. nBadPages += page == 3;
  1585. nTotalPages += page != 1;
  1586. }
  1587. DebugTraceMMgr("entry = %lld; size = %lld; free = %lld; system %lld; utilisation = %g%%, fragmentation = %g%%\n",
  1588. k, nSize, cs.FreeCount * nSize, csGB.FreeCount * nSize,
  1589. (N_CHUNK_SIZE - cs.FreeCount * nSize) * 100.0f / N_CHUNK_SIZE, 100.0f * nBadPages / Y_ARRAY_SIZE(pages));
  1590. nTotalAllocated += N_CHUNK_SIZE;
  1591. nTotalFree += cs.FreeCount * nSize;
  1592. nTotalBadPages += nBadPages;
  1593. }
  1594. SystemFree(entries);
  1595. DebugTraceMMgr("Total allocated = %llu, free = %lld, system = %lld, locked for future use %lld, utilisation = %g, fragmentation = %g\n",
  1596. nTotalAllocated, nTotalFree, nTotalGroupBlocks, nTotalLocked,
  1597. 100.0f * (nTotalAllocated - nTotalFree) / nTotalAllocated, 100.0f * nTotalBadPages / nTotalPages);
  1598. DebugTraceMMgr("Total %lld bytes used, %lld bytes in used pages\n", nTotalUsed, nTotalPages * N_PAGE_SIZE);
  1599. for (int nSizeIdx = 0; nSizeIdx < N_SIZES; ++nSizeIdx)
  1600. globalFreeLists[nSizeIdx].ReturnWholeList(wholeLists[nSizeIdx]);
  1601. blockFreeList.ReturnWholeList(bfList);
  1602. }
  1603. void FlushThreadFreeList() {
  1604. if (pThreadInfo)
  1605. MoveSingleThreadFreeToGlobal(pThreadInfo);
  1606. }
  1607. void DumpMemoryBlockUtilization() {
  1608. // move current thread free to global lists to get better statistics
  1609. FlushThreadFreeList();
  1610. {
  1611. TLFLockHolder ls(&LFGlobalLock);
  1612. DumpMemoryBlockUtilizationLocked();
  1613. }
  1614. }
  1615. //////////////////////////////////////////////////////////////////////////
  1616. // malloc api
  1617. static bool LFAlloc_SetParam(const char* param, const char* value) {
  1618. if (!strcmp(param, "LB_LIMIT_TOTAL_SIZE")) {
  1619. LB_LIMIT_TOTAL_SIZE = atoi(value);
  1620. return true;
  1621. }
  1622. if (!strcmp(param, "LB_LIMIT_TOTAL_SIZE_BYTES")) {
  1623. LB_LIMIT_TOTAL_SIZE = (atoi(value) + N_PAGE_SIZE - 1) / N_PAGE_SIZE;
  1624. return true;
  1625. }
  1626. #ifdef DBG_FILL_MEMORY
  1627. if (!strcmp(param, "FillMemoryOnAllocation")) {
  1628. FillMemoryOnAllocation = !strcmp(value, "true");
  1629. return true;
  1630. }
  1631. #endif
  1632. if (!strcmp(param, "TransparentHugePages")) {
  1633. TransparentHugePages = !strcmp(value, "true");
  1634. return true;
  1635. }
  1636. if (!strcmp(param, "MapHugeTLB")) {
  1637. MapHugeTLB = !strcmp(value, "true");
  1638. return true;
  1639. }
  1640. if (!strcmp(param, "EnableDefrag")) {
  1641. EnableDefrag = !strcmp(value, "true");
  1642. return true;
  1643. }
  1644. return false;
  1645. };
  1646. static const char* LFAlloc_GetParam(const char* param) {
  1647. struct TParam {
  1648. const char* Name;
  1649. const char* Value;
  1650. };
  1651. static const TParam Params[] = {
  1652. {"GetLFAllocCounterFast", (const char*)&GetLFAllocCounterFast},
  1653. {"GetLFAllocCounterFull", (const char*)&GetLFAllocCounterFull},
  1654. #if defined(LFALLOC_DBG)
  1655. {"SetThreadAllocTag", (const char*)&SetThreadAllocTag},
  1656. {"SetProfileCurrentThread", (const char*)&SetProfileCurrentThread},
  1657. {"SetProfileAllThreads", (const char*)&SetProfileAllThreads},
  1658. {"SetAllocationSamplingEnabled", (const char*)&SetAllocationSamplingEnabled},
  1659. {"SetAllocationSampleRate", (const char*)&SetAllocationSampleRate},
  1660. {"SetAllocationSampleMaxSize", (const char*)&SetAllocationSampleMaxSize},
  1661. {"SetAllocationCallback", (const char*)&SetAllocationCallback},
  1662. {"SetDeallocationCallback", (const char*)&SetDeallocationCallback},
  1663. {"GetPerTagAllocInfo", (const char*)&GetPerTagAllocInfo},
  1664. #endif // LFALLOC_DBG
  1665. };
  1666. for (int i = 0; i < Y_ARRAY_SIZE(Params); ++i) {
  1667. if (strcmp(param, Params[i].Name) == 0) {
  1668. return Params[i].Value;
  1669. }
  1670. }
  1671. return nullptr;
  1672. }
  1673. static Y_FORCE_INLINE int LFPosixMemalign(void** memptr, size_t alignment, size_t size) {
  1674. if (Y_UNLIKELY(alignment > 4096)) {
  1675. const char* error = "Larger alignment are not guaranteed with this implementation\n";
  1676. #ifdef _win_
  1677. OutputDebugStringA(error);
  1678. #endif
  1679. NMalloc::AbortFromCorruptedAllocator(error);
  1680. }
  1681. size_t bigsize = size;
  1682. if (bigsize <= alignment) {
  1683. bigsize = alignment;
  1684. } else if (bigsize < 2 * alignment) {
  1685. bigsize = 2 * alignment;
  1686. }
  1687. #if defined(LFALLOC_DBG)
  1688. if (alignment > sizeof(TAllocHeader)) {
  1689. bigsize += alignment;
  1690. }
  1691. #endif
  1692. *memptr = LFAlloc(bigsize);
  1693. #if defined(LFALLOC_DBG)
  1694. if (alignment > sizeof(TAllocHeader)) {
  1695. // memptr isn't aligned due to alloc header
  1696. const auto* header = GetAllocHeader(*memptr);
  1697. *memptr = (void*)((const char*) (*memptr) + alignment - sizeof(TAllocHeader));
  1698. // make fake header to retrieve original header ptr on dealloc
  1699. auto* next = GetAllocHeader(*memptr);
  1700. next->Tag = DBG_ALLOC_ALIGNED_TAG;
  1701. next->Size = (uint64_t)header;
  1702. next->Cookie = 0;
  1703. }
  1704. #endif
  1705. Y_ASSERT_NOBT((intptr_t)*memptr % alignment == 0);
  1706. return 0;
  1707. }
  1708. static Y_FORCE_INLINE void* LFVAlloc(size_t size) {
  1709. const size_t pg = N_PAGE_SIZE;
  1710. void* p = nullptr;
  1711. #if defined(LFALLOC_DBG)
  1712. LFPosixMemalign(&p, pg, size);
  1713. #else
  1714. size_t bigsize = (size + pg - 1) & (~(pg - 1));
  1715. p = LFAlloc(bigsize);
  1716. #endif
  1717. Y_ASSERT_NOBT((intptr_t)p % N_PAGE_SIZE == 0);
  1718. return p;
  1719. }
  1720. #endif