Skip to content

Commit e600bf1

Browse files
NeilMondayNeil Mondaymjp41
authored
Release all pagemap reservations at the very end of the program or DLL (#773)
* Release all pagemap reservations at the very end of the program or DLL Co-authored-by: Neil Monday <[email protected]> Co-authored-by: Matthew Parkinson <[email protected]>
1 parent 890bcf9 commit e600bf1

File tree

2 files changed

+278
-34
lines changed

2 files changed

+278
-34
lines changed

src/snmalloc/pal/pal_windows.h

Lines changed: 276 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,23 @@
2727
# endif
2828
# endif
2929

30+
/**
31+
* In order to properly cleanup our pagemap reservation,
32+
* we need to make sure we do it is done after all other
33+
* allocations have been freed.
34+
*
35+
* One way to guarantee that the reservations get released
36+
* at the absolute end of the program is to force them to
37+
* be initialized first. Statics and globals get destroyed
38+
* in FILO order of when they were initialized. The pragma
39+
* init_seg makes sure the statics and globals in this
40+
* file are handled first, and thus will be the last to
41+
* be destroyed when the program exits or the DLL is
42+
* unloaded.
43+
*/
44+
# pragma warning(disable : 4075)
45+
# pragma init_seg(".CRT$XCB")
46+
3047
namespace snmalloc
3148
{
3249
class PALWindows : public PalTimerDefaultImpl<PALWindows>
@@ -273,42 +290,10 @@ namespace snmalloc
273290

274291
# ifdef PLATFORM_HAS_VIRTUALALLOC2
275292
template<bool state_using>
276-
static void* reserve_aligned(size_t size) noexcept
277-
{
278-
SNMALLOC_ASSERT(bits::is_pow2(size));
279-
SNMALLOC_ASSERT(size >= minimum_alloc_size);
280-
281-
DWORD flags = MEM_RESERVE;
282-
283-
if (state_using)
284-
flags |= MEM_COMMIT;
285-
286-
// If we're on Windows 10 or newer, we can use the VirtualAlloc2
287-
// function. The FromApp variant is useable by UWP applications and
288-
// cannot allocate executable memory.
289-
MEM_ADDRESS_REQUIREMENTS addressReqs = {NULL, NULL, size};
290-
291-
MEM_EXTENDED_PARAMETER param = {
292-
{MemExtendedParameterAddressRequirements, 0}, {0}};
293-
// Separate assignment as MSVC doesn't support .Pointer in the
294-
// initialisation list.
295-
param.Pointer = &addressReqs;
296-
297-
void* ret = VirtualAlloc2FromApp(
298-
nullptr, nullptr, size, flags, PAGE_READWRITE, &param, 1);
299-
if (ret == nullptr)
300-
errno = ENOMEM;
301-
return ret;
302-
}
293+
static void* reserve_aligned(size_t size) noexcept;
303294
# endif
304295

305-
static void* reserve(size_t size) noexcept
306-
{
307-
void* ret = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
308-
if (ret == nullptr)
309-
errno = ENOMEM;
310-
return ret;
311-
}
296+
static void* reserve(size_t size) noexcept;
312297

313298
/**
314299
* Source of Entropy
@@ -370,5 +355,262 @@ namespace snmalloc
370355
}
371356
# endif
372357
};
358+
359+
/**
360+
* This VirtualVector class is an implementation of
361+
* a vector, but does not use new/malloc or any other
362+
* STL containers. We cannot use these after
363+
* init_seg(".CRT$XCB") usage, because the CRT
364+
* might not be fully initialized yet. The segments
365+
* compiler, lib, user cannot be used because of
366+
* CRT internals like std::locale facets
367+
*/
368+
class VirtualVector
369+
{
370+
void** data = nullptr;
371+
size_t size = 0;
372+
size_t committed_elements = 0;
373+
size_t reserved_elements = 0;
374+
375+
static constexpr size_t MinCommit =
376+
snmalloc::PALWindows::page_size / sizeof(void*);
377+
static constexpr size_t MinReserve =
378+
16 * snmalloc::PALWindows::page_size / sizeof(void*);
379+
380+
// Lock for the reserved ranges.
381+
inline static snmalloc::FlagWord push_back_lock{};
382+
383+
public:
384+
VirtualVector(
385+
size_t reserve_elems = MinReserve, size_t initial_commit = MinCommit)
386+
{
387+
reserve_and_commit(reserve_elems, initial_commit);
388+
}
389+
390+
~VirtualVector()
391+
{
392+
if (data)
393+
{
394+
for (size_t i = size; i > 0; i--)
395+
{
396+
size_t index = i - 1;
397+
if (data[index] == nullptr)
398+
continue;
399+
400+
BOOL ok = VirtualFree(data[index], 0, MEM_RELEASE);
401+
402+
data[index] = nullptr;
403+
404+
if (!ok)
405+
{
406+
snmalloc::PALWindows::error("VirtualFree failed");
407+
}
408+
}
409+
410+
BOOL ok = VirtualFree(data, 0, MEM_RELEASE);
411+
412+
data = nullptr;
413+
414+
if (!ok)
415+
{
416+
snmalloc::PALWindows::error("VirtualFree failed");
417+
}
418+
}
419+
}
420+
421+
void push_back(void* value)
422+
{
423+
snmalloc::FlagLock lock(push_back_lock);
424+
ensure_capacity();
425+
data[size++] = value;
426+
}
427+
428+
private:
429+
// Simple max function (avoiding <algorithm>)
430+
static size_t max_size_t(size_t a, size_t b)
431+
{
432+
return a > b ? a : b;
433+
}
434+
435+
void ensure_capacity()
436+
{
437+
if (size >= committed_elements)
438+
{
439+
size_t grow = max_size_t(MinCommit, committed_elements / 2);
440+
commit_more(committed_elements + grow);
441+
}
442+
443+
if (size >= reserved_elements)
444+
{
445+
grow_reserved();
446+
}
447+
}
448+
449+
void reserve_and_commit(size_t reserve_elems, size_t commit_elems)
450+
{
451+
size_t reserve_bytes = reserve_elems * sizeof(void*);
452+
void** new_block = (void**)VirtualAlloc(
453+
nullptr, reserve_bytes, MEM_RESERVE, PAGE_READWRITE);
454+
if (!new_block)
455+
snmalloc::PALWindows::error("VirtualAlloc failed");
456+
457+
size_t commit_bytes = commit_elems * sizeof(void*);
458+
if (!VirtualAlloc(new_block, commit_bytes, MEM_COMMIT, PAGE_READWRITE))
459+
{
460+
VirtualFree(new_block, 0, MEM_RELEASE);
461+
snmalloc::PALWindows::error("VirtualAlloc failed");
462+
}
463+
464+
data = new_block;
465+
reserved_elements = reserve_elems;
466+
committed_elements = commit_elems;
467+
}
468+
469+
void commit_more(size_t new_commit_elements)
470+
{
471+
if (new_commit_elements > reserved_elements)
472+
{
473+
grow_reserved();
474+
return;
475+
}
476+
477+
size_t old_bytes = committed_elements * sizeof(void*);
478+
size_t new_bytes = new_commit_elements * sizeof(void*);
479+
size_t commit_bytes = new_bytes - old_bytes;
480+
481+
if (commit_bytes > 0)
482+
{
483+
void* result = VirtualAlloc(
484+
(char*)data + old_bytes, commit_bytes, MEM_COMMIT, PAGE_READWRITE);
485+
if (!result)
486+
error("VirtualAlloc failed");
487+
488+
committed_elements = new_commit_elements;
489+
}
490+
}
491+
492+
void grow_reserved()
493+
{
494+
size_t new_reserved =
495+
reserved_elements == 0 ? MinReserve : reserved_elements * 2;
496+
size_t new_commit = max_size_t(committed_elements, size + MinCommit);
497+
498+
void** new_block = (void**)VirtualAlloc(
499+
nullptr, new_reserved * sizeof(void*), MEM_RESERVE, PAGE_READWRITE);
500+
if (!new_block)
501+
error("VirtualAlloc failed");
502+
503+
if (!VirtualAlloc(
504+
new_block, new_commit * sizeof(void*), MEM_COMMIT, PAGE_READWRITE))
505+
{
506+
VirtualFree(new_block, 0, MEM_RELEASE);
507+
error("VirtualAlloc failed");
508+
}
509+
510+
// Copy existing values
511+
for (size_t i = 0; i < size; ++i)
512+
{
513+
new_block[i] = data[i];
514+
}
515+
516+
VirtualFree(data, 0, MEM_RELEASE);
517+
data = new_block;
518+
reserved_elements = new_reserved;
519+
committed_elements = new_commit;
520+
}
521+
522+
public:
523+
void*& operator[](size_t index)
524+
{
525+
return data[index];
526+
}
527+
528+
const void* operator[](size_t index) const
529+
{
530+
return data[index];
531+
}
532+
533+
size_t get_size() const
534+
{
535+
return size;
536+
}
537+
538+
size_t get_capacity() const
539+
{
540+
return committed_elements;
541+
}
542+
543+
void** begin()
544+
{
545+
return data;
546+
}
547+
548+
void** end()
549+
{
550+
return data + size;
551+
}
552+
553+
const void* const* begin() const
554+
{
555+
return data;
556+
}
557+
558+
const void* const* end() const
559+
{
560+
return data + size;
561+
}
562+
};
563+
564+
/**
565+
* This will be destroyed last of all of the
566+
* statics and globals due to init_seg
567+
*/
568+
static inline VirtualVector reservations;
569+
570+
# ifdef PLATFORM_HAS_VIRTUALALLOC2
571+
template<bool state_using>
572+
void* PALWindows::reserve_aligned(size_t size) noexcept
573+
{
574+
SNMALLOC_ASSERT(bits::is_pow2(size));
575+
SNMALLOC_ASSERT(size >= minimum_alloc_size);
576+
577+
DWORD flags = MEM_RESERVE;
578+
579+
if (state_using)
580+
flags |= MEM_COMMIT;
581+
582+
// If we're on Windows 10 or newer, we can use the VirtualAlloc2
583+
// function. The FromApp variant is useable by UWP applications and
584+
// cannot allocate executable memory.
585+
MEM_ADDRESS_REQUIREMENTS addressReqs = {NULL, NULL, size};
586+
587+
MEM_EXTENDED_PARAMETER param = {
588+
{MemExtendedParameterAddressRequirements, 0}, {0}};
589+
// Separate assignment as MSVC doesn't support .Pointer in the
590+
// initialisation list.
591+
param.Pointer = &addressReqs;
592+
593+
void* ret = VirtualAlloc2FromApp(
594+
nullptr, nullptr, size, flags, PAGE_READWRITE, &param, 1);
595+
if (ret == nullptr)
596+
errno = ENOMEM;
597+
598+
reservations.push_back(ret);
599+
600+
return ret;
601+
}
602+
# endif
603+
604+
void* PALWindows::reserve(size_t size) noexcept
605+
{
606+
void* ret = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
607+
if (ret == nullptr)
608+
errno = ENOMEM;
609+
610+
reservations.push_back(ret);
611+
612+
return ret;
613+
}
614+
373615
}
374616
#endif

src/test/perf/lotsofthreads/lotsofthread.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ int main()
9696
size_t iterations = 50000;
9797
#elif defined(__APPLE__) && !defined(SNMALLOC_APPLE_HAS_OS_SYNC_WAIT_ON_ADDRESS)
9898
size_t iterations = 50000;
99+
#elif defined(WIN32)
100+
size_t iterations = 50000;
99101
#else
100102
size_t iterations = 200000;
101103
#endif

0 commit comments

Comments
 (0)