From 2095728d19a147a5c64cdf61d5ea3014e474e863 Mon Sep 17 00:00:00 2001 From: montjeffrey Date: Tue, 13 Oct 2026 15:35:07 -0400 Subject: [PATCH] Allocate Win32 thread server buffers on first use blas_thread_server allocated its GEMM buffer as the first statement of every worker thread. On Windows that allocation is VirtualAlloc with MEM_RESERVE | MEM_COMMIT, so each idle worker added BUFFER_SIZE to the commit charge of any process that merely loads the library: with the 32 MB buffer used by the numpy wheels, 19 workers on a 20-thread CPU committed 608 MB before a single BLAS call. Allocate the buffer the first time a queued job needs it (sa == NULL), as the pthreads server already does, and only free it if it was allocated. Measured on a 20-thread Windows 11 machine (MinGW-w64 gcc 15, HASWELL, BUFFERSIZE=20, NUM_THREADS=24), private bytes: after load, no BLAS call: 610.6 MB -> 1.5 MB after a 4-thread dgemm: 642.8 MB -> 129.8 MB after all 20 threads have run: 643.0 MB -> 643.0 MB (unchanged) ctest passes 16/16, dgemm/sgemm/zgemm results match a naive reference, and 8 concurrent callers x 40 threaded dgemm from a cold start show no mismatches. Refs #5587 --- driver/others/blas_server_win32.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/driver/others/blas_server_win32.c b/driver/others/blas_server_win32.c index e827ceea0b..0fc78eafa6 100644 --- a/driver/others/blas_server_win32.c +++ b/driver/others/blas_server_win32.c @@ -206,13 +206,16 @@ static DWORD WINAPI blas_thread_server(void *arg){ BLASLONG cpu = (BLASLONG)arg; #endif - void *buffer, *sa, *sb; + void *buffer = NULL, *sa, *sb; blas_queue_t *queue; DWORD action; HANDLE handles[] = {pool.filled, pool.killed}; - /* Each server needs each buffer */ - buffer = blas_memory_alloc(2); + /* Each server needs its own buffer, but allocate it on first use rather + than at thread start: on Windows the allocation is committed up front + (VirtualAlloc with MEM_COMMIT), so an eager buffer per idle thread adds + BUFFER_SIZE * (threads - 1) to the commit charge of every process that + merely loads the library. The pthreads server allocates lazily too. */ #ifdef SMP_DEBUG fprintf(STDERR, "Server[%2ld] Thread is started!\n", cpu); @@ -267,7 +270,10 @@ static DWORD WINAPI blas_thread_server(void *arg){ main_status[cpu] = MAIN_RUNNING1; #endif - if (sa == NULL) sa = (void *)((BLASLONG)buffer + GEMM_OFFSET_A); + if (sa == NULL) { + if (buffer == NULL) buffer = blas_memory_alloc(2); + sa = (void *)((BLASLONG)buffer + GEMM_OFFSET_A); + } if (sb == NULL) { if (!(queue -> mode & BLAS_COMPLEX)){ @@ -348,7 +354,7 @@ static DWORD WINAPI blas_thread_server(void *arg){ fprintf(STDERR, "Server[%2ld] Shutdown!\n", cpu); #endif - blas_memory_free(buffer); + if (buffer != NULL) blas_memory_free(buffer); return 0; }