diff options
Diffstat (limited to 'v_windows/v/vlib/builtin/prealloc.c.v')
-rw-r--r-- | v_windows/v/vlib/builtin/prealloc.c.v | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/v_windows/v/vlib/builtin/prealloc.c.v b/v_windows/v/vlib/builtin/prealloc.c.v new file mode 100644 index 0000000..0ef66fc --- /dev/null +++ b/v_windows/v/vlib/builtin/prealloc.c.v @@ -0,0 +1,114 @@ +module builtin + +// With -prealloc, V calls libc's malloc to get chunks, each at least 16MB +// in size, as needed. Once a chunk is available, all malloc() calls within +// V code, that can fit inside the chunk, will use it instead, each bumping a +// pointer, till the chunk is filled. Once a chunk is filled, a new chunk will +// be allocated by calling libc's malloc, and the process continues. +// Each new chunk has a pointer to the old one, and at the end of the program, +// the entire linked list of chunks is freed. +// The goal of all this is to amortize the cost of calling libc's malloc, +// trading higher memory usage for a compiler (or any single threaded batch +// mode program), for a ~8-10% speed increase. +// NB: `-prealloc` is NOT safe to be used for multithreaded programs! + +// size of the preallocated chunk +const prealloc_block_size = 16 * 1024 * 1024 + +__global g_memory_block &VMemoryBlock +[heap] +struct VMemoryBlock { +mut: + id int + cap int + start &byte = 0 + previous &VMemoryBlock = 0 + remaining int + current &byte = 0 + mallocs int +} + +[unsafe] +fn vmemory_block_new(prev &VMemoryBlock, at_least int) &VMemoryBlock { + mut v := unsafe { &VMemoryBlock(C.calloc(1, sizeof(VMemoryBlock))) } + if prev != 0 { + v.id = prev.id + 1 + } + v.previous = prev + block_size := if at_least < prealloc_block_size { prealloc_block_size } else { at_least } + v.start = unsafe { C.malloc(block_size) } + v.cap = block_size + v.remaining = block_size + v.current = v.start + return v +} + +[unsafe] +fn vmemory_block_malloc(n int) &byte { + unsafe { + if g_memory_block.remaining < n { + g_memory_block = vmemory_block_new(g_memory_block, n) + } + mut res := &byte(0) + res = g_memory_block.current + g_memory_block.remaining -= n + g_memory_block.mallocs++ + g_memory_block.current += n + return res + } +} + +///////////////////////////////////////////////// + +[unsafe] +fn prealloc_vinit() { + unsafe { + g_memory_block = vmemory_block_new(voidptr(0), prealloc_block_size) + $if !freestanding { + C.atexit(prealloc_vcleanup) + } + } +} + +[unsafe] +fn prealloc_vcleanup() { + $if prealloc_stats ? { + // NB: we do 2 loops here, because string interpolation + // in the first loop may still use g_memory_block + // The second loop however should *not* allocate at all. + mut nr_mallocs := i64(0) + mut mb := g_memory_block + for mb != 0 { + nr_mallocs += mb.mallocs + eprintln('> freeing mb.id: ${mb.id:3} | cap: ${mb.cap:7} | rem: ${mb.remaining:7} | start: ${voidptr(mb.start)} | current: ${voidptr(mb.current)} | diff: ${u64(mb.current) - u64(mb.start):7} bytes | mallocs: $mb.mallocs') + mb = mb.previous + } + eprintln('> nr_mallocs: $nr_mallocs') + } + unsafe { + for g_memory_block != 0 { + C.free(g_memory_block.start) + g_memory_block = g_memory_block.previous + } + } +} + +[unsafe] +fn prealloc_malloc(n int) &byte { + return unsafe { vmemory_block_malloc(n) } +} + +[unsafe] +fn prealloc_realloc(old_data &byte, old_size int, new_size int) &byte { + new_ptr := unsafe { vmemory_block_malloc(new_size) } + min_size := if old_size < new_size { old_size } else { new_size } + unsafe { C.memcpy(new_ptr, old_data, min_size) } + return new_ptr +} + +[unsafe] +fn prealloc_calloc(n int) &byte { + new_ptr := unsafe { vmemory_block_malloc(n) } + unsafe { C.memset(new_ptr, 0, n) } + return new_ptr +} |