Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions changelog/gc_parallel.dd
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
GC now marks the heap with multiple threads

The garbage collector now uses available CPU cores to mark the heap
faster. This reduces pause times for a collection considerably.

By default, the GC uses all available logical cores of your CPU. This
might affect your application if it has threads that are not suspended
during the mark phase of the collection. You can configure the number of
additional threads used for marking by DRT option `parallel` to the
$(LINK2 $(ROOT_DIR)spec/garbage.html, GC configuration),
e.g. by passing `--DRT-gcopt=parallel:2` on the command
line. A value of `0` disables parallel marking completely.

As usual, you can also embed the configuration into the application by
redefining `rt_options`, e.g.

-------
extern(C) __gshared string[] rt_options = [ "gcopt=parallel:0" ];
-------
5 changes: 4 additions & 1 deletion src/core/gc/config.d
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct Config
size_t minPoolSize = 1; // initial and minimum pool size (MB)
size_t maxPoolSize = 64; // maximum pool size (MB)
size_t incPoolSize = 3; // pool size increment (MB)
uint parallel = 99; // number of additional threads for marking (limited by cpuid.threadsPerCPU-1)
float heapSizeFactor = 2.0; // heap size to used memory ratio
string cleanup = "collect"; // select gc cleanup method none|collect|finalize

Expand Down Expand Up @@ -51,11 +52,13 @@ struct Config
minPoolSize:N - initial and minimum pool size in MB (%lld)
maxPoolSize:N - maximum pool size in MB (%lld)
incPoolSize:N - pool size increment MB (%lld)
parallel:N - number of additional threads for marking (%lld)
heapSizeFactor:N - targeted heap size to used memory ratio (%g)
cleanup:none|collect|finalize - how to treat live objects when terminating (collect)
".ptr,
cast(long)initReserve, cast(long)minPoolSize,
cast(long)maxPoolSize, cast(long)incPoolSize, heapSizeFactor);
cast(long)maxPoolSize, cast(long)incPoolSize,
cast(long)parallel, heapSizeFactor);
}

string errorName() @nogc nothrow { return "GC"; }
Expand Down
95 changes: 85 additions & 10 deletions src/core/sys/windows/dll.d
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ extern (C) // rt.minfo
}

private:
version (Win32)
{
struct dll_aux
{
// don't let symbols leak into other modules
version (Win32)
{
struct LdrpTlsListEntry
{
LdrpTlsListEntry* next;
Expand Down Expand Up @@ -225,6 +225,7 @@ struct dll_aux
// let the old array leak, in case a oncurrent thread is still relying on it
return true;
}
} // Win32

alias bool BOOLEAN;

Expand All @@ -241,7 +242,8 @@ struct dll_aux
LIST_ENTRY* prev;
}

// the following structures can be found here: http://undocumented.ntinternals.net/
// the following structures can be found here:
// https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/ldr_data_table_entry.htm
// perhaps this should be same as LDR_DATA_TABLE_ENTRY, which is introduced with PEB_LDR_DATA
struct LDR_MODULE
{
Expand All @@ -254,10 +256,22 @@ struct dll_aux
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT LoadCount; // obsolete after Version 6.1
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
LDR_DDAG_NODE *DdagNode; // starting with Version 6.2
}

struct LDR_DDAG_NODE
{
LIST_ENTRY Modules;
void* ServiceTagList; // LDR_SERVICE_TAG_RECORD
ULONG LoadCount;
ULONG ReferenceCount; // Version 10: ULONG LoadWhileUnloadingCount;
ULONG DependencyCount; // Version 10: ULONG LowestLink;
}

struct PEB_LDR_DATA
Expand All @@ -270,7 +284,7 @@ struct dll_aux
LIST_ENTRY InInitializationOrderModuleList;
}

static LDR_MODULE* findLdrModule( HINSTANCE hInstance, void** peb ) nothrow
static LDR_MODULE* findLdrModule( HINSTANCE hInstance, void** peb ) nothrow @nogc
{
PEB_LDR_DATA* ldrData = cast(PEB_LDR_DATA*) peb[3];
LIST_ENTRY* root = &ldrData.InLoadOrderModuleList;
Expand All @@ -294,7 +308,6 @@ struct dll_aux
return true;
}
}
}

public:
/* *****************************************************
Expand Down Expand Up @@ -359,6 +372,63 @@ bool dll_fixTLS( HINSTANCE hInstance, void* tlsstart, void* tlsend, void* tls_ca
}
}

private extern (Windows) ULONGLONG VerSetConditionMask(ULONGLONG, DWORD, BYTE) nothrow @nogc;

private bool isWindows8OrLater() nothrow @nogc
{
OSVERSIONINFOEXW osvi;
osvi.dwOSVersionInfoSize = osvi.sizeof;
DWORDLONG dwlConditionMask = VerSetConditionMask(
VerSetConditionMask(
VerSetConditionMask(
0, VER_MAJORVERSION, VER_GREATER_EQUAL),
VER_MINORVERSION, VER_GREATER_EQUAL),
VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);

osvi.dwMajorVersion = 6;
osvi.dwMinorVersion = 2;
osvi.wServicePackMajor = 0;

return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE;
}

/* *****************************************************
* Get the process reference count for the given DLL handle
* Params:
* hInstance = DLL instance handle
* Returns:
* the reference count for the DLL in the current process,
* -1 if the DLL is implicitely loaded with the process
* or -2 if the DLL handle is invalid
*/
int dll_getRefCount( HINSTANCE hInstance ) nothrow @nogc
{
void** peb;
version (Win64)
{
asm pure nothrow @nogc
{
mov RAX, 0x60;
mov RAX,GS:[RAX];
mov peb, RAX;
}
}
else version (Win32)
{
asm pure nothrow @nogc
{
mov EAX,FS:[0x30];
mov peb, EAX;
}
}
dll_aux.LDR_MODULE *ldrMod = dll_aux.findLdrModule( hInstance, peb );
if ( !ldrMod )
return -2; // not in module list, bail out
if (isWindows8OrLater())
return ldrMod.DdagNode.LoadCount;
return ldrMod.LoadCount;
}

// fixup TLS storage, initialize runtime and attach to threads
// to be called from DllMain with reason DLL_PROCESS_ATTACH
bool dll_process_attach( HINSTANCE hInstance, bool attach_threads,
Expand Down Expand Up @@ -415,11 +485,16 @@ void dll_process_detach( HINSTANCE hInstance, bool detach_threads = true )
// detach from all other threads
if ( detach_threads )
enumProcessThreads(
function (uint id, void* context) {
if ( id != GetCurrentThreadId() && thread_findByAddr( id ) )
function (uint id, void* context)
{
if ( id != GetCurrentThreadId() )
{
thread_moduleTlsDtor( id );
thread_detachByAddr( id );
if ( auto t = thread_findByAddr( id ) )
{
thread_moduleTlsDtor( id );
if ( !t.isMainThread() )
thread_detachByAddr( id );
}
}
return true;
}, null );
Expand Down
Loading