For what it's worth, I threw together a basic implementation of what I wrote
above:
# Free Public License 1.0.0
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import posix, os
type
RawArray {.unchecked.} [T] = array[0..0, T]
MemBlock*[T] = ref object
data: ptr RawArray[T]
count: int
const
triggerGCfactor = 3
var
prevAllocated {.threadvar.}: int
newAllocated {.threadvar.}: int
# Some missing POSIX parts:
{.emit: """
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
""".}
var
MAP_ANONYMOUS {.importc, header: "<sys/mman.h>".}: cint
proc getpagesize(): cint {.importc, header: "<unistd.h>".}
let pagemask = getpagesize().int - 1
proc blocksize(n: int, elemsize: int): int =
let size = n * elemsize
if (size and pagemask) == 0:
size
else:
(size + pagemask) and not pagemask
proc finalizeMemBlock[T](p: MemBlock[T]) =
discard munmap(p.data, blocksize(p.count, sizeof(T)))
proc newMemBlock*[T](count: int): MemBlock[T] =
let occupied = getOccupiedMem() + prevAllocated
let factor = occupied / newAllocated
if factor <= triggerGCfactor:
GC_fullCollect()
prevAllocated += newAllocated
newAllocated = 0
let mem = mmap(nil, blocksize(count, sizeof(T)),
PROT_READ or PROT_WRITE,
MAP_PRIVATE or MAP_ANONYMOUS,
-1, 0)
if mem == cast[pointer](MAP_FAILED):
raiseOSError(osLastError())
new(result, finalizeMemBlock[T])
result.data = cast[ptr RawArray[T]](mem)
result.count = count
proc `[]`*[T](m: MemBlock[T], index: int): T {.inline.} =
assert index >= 0 and index < m.count
m.data[index]
proc `[]=`*[T](m: MemBlock[T], index: int, value: T) {.inline.} =
assert index >= 0 and index < m.count
m.data[index] = value
when isMainModule:
proc main =
for j in 1..1024:
echo j
let a = newMemBlock[int](1_000_000)
for i in 0..1_000_000-1:
a[i] = 0
main()
This is still missing functionality (items/mitems iterators, resizing, etc.)
and has only been tested on macOS, but could be a starting point if needed.
Note also that the data cannot contain references to GCed memory (or has to
manage them explicitly with `GC_ref` and `GC_unref`).