// Copyright 2017 The Memory Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package memory implements a memory allocator. // // Build status // // available at https://modern-c.appspot.com/-/builder/?importpath=modernc.org%2fmemory // // Changelog // // 2017-10-03 Added alternative, unsafe.Pointer-based API. // // Package memory implements a memory allocator. // // Changelog // // 2017-10-03 Added alternative, unsafe.Pointer-based API. // // Benchmarks // // AMD Ryzen 9 3900X 12-Core Processor × 24 // // jnml@3900x:~/src/modernc.org/memory$ date ; go version ; go test -run @ -bench . -benchmem |& tee log // Fri Nov 20 17:23:04 CET 2020 // go version go1.15.5 linux/amd64 // goos: linux // goarch: amd64 // pkg: modernc.org/memory // BenchmarkFree16-24 141188362 8.26 ns/op 0 B/op 0 allocs/op // BenchmarkFree32-24 100000000 11.4 ns/op 0 B/op 0 allocs/op // BenchmarkFree64-24 67160647 18.3 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc16-24 60612698 19.8 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc32-24 47968105 23.8 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc64-24 40752181 28.6 ns/op 0 B/op 0 allocs/op // BenchmarkGoCalloc16-24 66487354 17.8 ns/op 16 B/op 1 allocs/op // BenchmarkGoCalloc32-24 56009206 21.2 ns/op 32 B/op 1 allocs/op // BenchmarkGoCalloc64-24 52086571 23.4 ns/op 64 B/op 1 allocs/op // BenchmarkMalloc16-24 113943390 10.2 ns/op 0 B/op 0 allocs/op // BenchmarkMalloc32-24 113520471 10.2 ns/op 0 B/op 0 allocs/op // BenchmarkMalloc64-24 108787056 10.7 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree16-24 146110286 7.94 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree32-24 93052707 12.0 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree64-24 69805262 17.3 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc16-24 85282725 13.7 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc32-24 66489789 17.9 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc64-24 53561092 22.7 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc16-24 222978858 5.28 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc32-24 210443384 5.30 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc64-24 213706227 5.47 ns/op 0 B/op 0 allocs/op // PASS // ok modernc.org/memory 70.528s // jnml@3900x:~/src/modernc.org/memory$ // // Intel® Core™ i5-4670 CPU @ 3.40GHz × 4 // // ==== jnml@4670:~/src/modernc.org/memory> date ; go version ; go test -run @ -bench . -benchmem |& tee log // Sat Dec 8 12:56:53 CET 2018 // go version go1.11.2 linux/amd64 // goos: linux // goarch: amd64 // pkg: modernc.org/memory // BenchmarkFree16-4 100000000 14.7 ns/op 0 B/op 0 allocs/op // BenchmarkFree32-4 100000000 20.5 ns/op 0 B/op 0 allocs/op // BenchmarkFree64-4 50000000 32.8 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc16-4 50000000 24.4 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc32-4 50000000 29.2 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc64-4 50000000 35.7 ns/op 0 B/op 0 allocs/op // BenchmarkGoCalloc16-4 50000000 27.0 ns/op 16 B/op 1 allocs/op // BenchmarkGoCalloc32-4 50000000 27.3 ns/op 32 B/op 1 allocs/op // BenchmarkGoCalloc64-4 30000000 37.9 ns/op 64 B/op 1 allocs/op // BenchmarkMalloc16-4 100000000 12.9 ns/op 0 B/op 0 allocs/op // BenchmarkMalloc32-4 100000000 12.9 ns/op 0 B/op 0 allocs/op // BenchmarkMalloc64-4 100000000 13.2 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree16-4 100000000 12.0 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree32-4 100000000 17.5 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree64-4 50000000 28.9 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc16-4 100000000 17.8 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc32-4 100000000 22.9 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc64-4 50000000 29.6 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc16-4 200000000 7.31 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc32-4 200000000 7.47 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc64-4 200000000 7.68 ns/op 0 B/op 0 allocs/op // PASS // ok modernc.org/memory 73.859s // // // Intel® Xeon(R) CPU E5-1650 v2 @ 3.50GHz × 12 // // ==== jnml@e5-1650:~/src/modernc.org/memory> date ; go version ; go test -run @ -bench . -benchmem // Fri Dec 7 14:18:50 CET 2018 // go version go1.11.2 linux/amd64 // goos: linux // goarch: amd64 // pkg: modernc.org/memory // BenchmarkFree16-12 100000000 16.7 ns/op 0 B/op 0 allocs/op // BenchmarkFree32-12 50000000 25.0 ns/op 0 B/op 0 allocs/op // BenchmarkFree64-12 30000000 39.7 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc16-12 50000000 26.3 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc32-12 50000000 33.4 ns/op 0 B/op 0 allocs/op // BenchmarkCalloc64-12 30000000 38.3 ns/op 0 B/op 0 allocs/op // BenchmarkGoCalloc16-12 50000000 26.6 ns/op 16 B/op 1 allocs/op // BenchmarkGoCalloc32-12 50000000 26.8 ns/op 32 B/op 1 allocs/op // BenchmarkGoCalloc64-12 30000000 35.1 ns/op 64 B/op 1 allocs/op // BenchmarkMalloc16-12 100000000 13.5 ns/op 0 B/op 0 allocs/op // BenchmarkMalloc32-12 100000000 13.4 ns/op 0 B/op 0 allocs/op // BenchmarkMalloc64-12 100000000 14.1 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree16-12 100000000 14.4 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree32-12 100000000 21.7 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrFree64-12 50000000 36.7 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc16-12 100000000 20.4 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc32-12 50000000 27.1 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrCalloc64-12 50000000 33.4 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc16-12 200000000 8.02 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc32-12 200000000 8.28 ns/op 0 B/op 0 allocs/op // BenchmarkUintptrMalloc64-12 200000000 8.29 ns/op 0 B/op 0 allocs/op // PASS // ok modernc.org/memory 80.896s
package memory // import "modernc.org/memory" import ( ) const ( headerSize = unsafe.Sizeof(page{}) mallocAllign = 2 * unsafe.Sizeof(uintptr(0)) maxSlotSize = 1 << maxSlotSizeLog maxSlotSizeLog = pageSizeLog - 2 pageAvail = pageSize - headerSize pageMask = pageSize - 1 pageSize = 1 << pageSizeLog ) func () { if unsafe.Sizeof(page{})%mallocAllign != 0 { panic("internal error") } } // if n%m != 0 { n += m-n%m }. m must be a power of 2. func (, int) int { return ( + - 1) &^ ( - 1) } type node struct { prev, next uintptr // *node } type page struct { brk int log uint size int used int } // Allocator allocates and frees memory. Its zero value is ready for use. The // exported counters are updated only when build tag memory.counters is // present. type Allocator struct { Allocs int // # of allocs. Bytes int // Asked from OS. cap [64]int lists [64]uintptr // *node Mmaps int // Asked from OS. pages [64]uintptr // *page regs map[uintptr]struct{} // map[*page]struct{} } func ( *Allocator) ( int) (uintptr /* *page */, error) { , , := mmap() if != nil { return 0, } if counters { .Mmaps++ .Bytes += } if .regs == nil { .regs = map[uintptr]struct{}{} } (*page)(unsafe.Pointer()).size = .regs[] = struct{}{} return , nil } func ( *Allocator) ( int) (uintptr /* *page */, error) { += int(headerSize) , := .mmap() if != nil { return 0, } (*page)(unsafe.Pointer()).log = 0 return , nil } func ( *Allocator) ( uint) (uintptr /* *page */, error) { if .cap[] == 0 { .cap[] = int(pageAvail) / (1 << ) } := int(headerSize) + .cap[]<< , := .mmap() if != nil { return 0, } .pages[] = (*page)(unsafe.Pointer()).log = return , nil } func ( *Allocator) ( uintptr /* *page */) error { delete(.regs, ) if counters { .Mmaps-- } return unmap(, (*page)(unsafe.Pointer()).size) } // UintptrCalloc is like Calloc except it returns an uintptr. func ( *Allocator) ( int) ( uintptr, error) { if trace { defer func() { fmt.Fprintf(os.Stderr, "Calloc(%#x) %#x, %v\n", , , ) }() } if , = .UintptrMalloc(); == 0 || != nil { return 0, } := ((*rawmem)(unsafe.Pointer()))[::] for := range { [] = 0 } return , nil } // UintptrFree is like Free except its argument is an uintptr, which must have // been acquired from UintptrCalloc or UintptrMalloc or UintptrRealloc. func ( *Allocator) ( uintptr) ( error) { if trace { defer func() { fmt.Fprintf(os.Stderr, "Free(%#x) %v\n", , ) }() } if == 0 { return nil } if counters { .Allocs-- } := &^ uintptr(pageMask) := (*page)(unsafe.Pointer()).log if == 0 { if counters { .Bytes -= (*page)(unsafe.Pointer()).size } return .unmap() } (*node)(unsafe.Pointer()).prev = 0 (*node)(unsafe.Pointer()).next = .lists[] if := (*node)(unsafe.Pointer()).next; != 0 { (*node)(unsafe.Pointer()).prev = } .lists[] = (*page)(unsafe.Pointer()).used-- if (*page)(unsafe.Pointer()).used != 0 { return nil } for := 0; < (*page)(unsafe.Pointer()).brk; ++ { := + headerSize + uintptr()<< := (*node)(unsafe.Pointer()).next := (*node)(unsafe.Pointer()).prev switch { case == 0: .lists[] = if != 0 { (*node)(unsafe.Pointer()).prev = 0 } case == 0: (*node)(unsafe.Pointer()).next = 0 default: (*node)(unsafe.Pointer()).next = (*node)(unsafe.Pointer()).prev = } } if .pages[] == { .pages[] = 0 } if counters { .Bytes -= (*page)(unsafe.Pointer()).size } return .unmap() } // UintptrMalloc is like Malloc except it returns an uinptr. func ( *Allocator) ( int) ( uintptr, error) { if trace { defer func() { fmt.Fprintf(os.Stderr, "Malloc(%#x) %#x, %v\n", , , ) }() } if < 0 { panic("invalid malloc size") } if == 0 { return 0, nil } if counters { .Allocs++ } := uint(bits.Len(uint((+int(mallocAllign)-1)&^int(mallocAllign-1) - 1))) if > maxSlotSizeLog { , := .newPage() if != nil { return 0, } return + headerSize, nil } if .lists[] == 0 && .pages[] == 0 { if , := .newSharedPage(); != nil { return 0, } } if := .pages[]; != 0 { (*page)(unsafe.Pointer()).used++ (*page)(unsafe.Pointer()).brk++ if (*page)(unsafe.Pointer()).brk == .cap[] { .pages[] = 0 } return + headerSize + uintptr((*page)(unsafe.Pointer()).brk-1)<<, nil } := .lists[] := &^ uintptr(pageMask) .lists[] = (*node)(unsafe.Pointer()).next if := (*node)(unsafe.Pointer()).next; != 0 { (*node)(unsafe.Pointer()).prev = 0 } (*page)(unsafe.Pointer()).used++ return , nil } // UintptrRealloc is like Realloc except its first argument is an uintptr, // which must have been returned from UintptrCalloc, UintptrMalloc or // UintptrRealloc. func ( *Allocator) ( uintptr, int) ( uintptr, error) { if trace { defer func() { fmt.Fprintf(os.Stderr, "UnsafeRealloc(%#x, %#x) %#x, %v\n", , , , ) }() } switch { case == 0: return .UintptrMalloc() case == 0 && != 0: return 0, .UintptrFree() } := UintptrUsableSize() if > { return , nil } if , = .UintptrMalloc(); != nil { return 0, } if < { = } copy((*rawmem)(unsafe.Pointer())[::], (*rawmem)(unsafe.Pointer())[::]) return , .UintptrFree() } // UintptrUsableSize is like UsableSize except its argument is an uintptr, // which must have been returned from UintptrCalloc, UintptrMalloc or // UintptrRealloc. func ( uintptr) ( int) { if trace { defer func() { fmt.Fprintf(os.Stderr, "UsableSize(%#x) %#x\n", , ) }() } if == 0 { return 0 } return usableSize() } func ( uintptr) ( int) { := &^ uintptr(pageMask) if := (*page)(unsafe.Pointer()).log; != 0 { return 1 << } return (*page)(unsafe.Pointer()).size - int(headerSize) } // Calloc is like Malloc except the allocated memory is zeroed. func ( *Allocator) ( int) ( []byte, error) { , := .UintptrCalloc() if != nil { return nil, } var []byte := (*reflect.SliceHeader)(unsafe.Pointer(&)) .Cap = usableSize() .Data = .Len = return , nil } // Close releases all OS resources used by a and sets it to its zero value. // // It's not necessary to Close the Allocator when exiting a process. func ( *Allocator) () ( error) { for := range .regs { if := .unmap(); != nil && == nil { = } } * = Allocator{} return } // Free deallocates memory (as in C.free). The argument of Free must have been // acquired from Calloc or Malloc or Realloc. func ( *Allocator) ( []byte) ( error) { if = [:cap()]; len() == 0 { return nil } return .UintptrFree(uintptr(unsafe.Pointer(&[0]))) } // Malloc allocates size bytes and returns a byte slice of the allocated // memory. The memory is not initialized. Malloc panics for size < 0 and // returns (nil, nil) for zero size. // // It's ok to reslice the returned slice but the result of appending to it // cannot be passed to Free or Realloc as it may refer to a different backing // array afterwards. func ( *Allocator) ( int) ( []byte, error) { , := .UintptrMalloc() if == 0 || != nil { return nil, } := (*reflect.SliceHeader)(unsafe.Pointer(&)) .Cap = usableSize() .Data = .Len = return , nil } // Realloc changes the size of the backing array of b to size bytes or returns // an error, if any. The contents will be unchanged in the range from the // start of the region up to the minimum of the old and new sizes. If the // new size is larger than the old size, the added memory will not be // initialized. If b's backing array is of zero size, then the call is // equivalent to Malloc(size), for all values of size; if size is equal to // zero, and b's backing array is not of zero size, then the call is equivalent // to Free(b). Unless b's backing array is of zero size, it must have been // returned by an earlier call to Malloc, Calloc or Realloc. If the area // pointed to was moved, a Free(b) is done. func ( *Allocator) ( []byte, int) ( []byte, error) { var uintptr if = [:cap()]; len() != 0 { = uintptr(unsafe.Pointer(&[0])) } if , = .UintptrRealloc(, ); == 0 || != nil { return nil, } := (*reflect.SliceHeader)(unsafe.Pointer(&)) .Cap = usableSize() .Data = .Len = return , nil } // UsableSize reports the size of the memory block allocated at p, which must // point to the first byte of a slice returned from Calloc, Malloc or Realloc. // The allocated memory block size can be larger than the size originally // requested from Calloc, Malloc or Realloc. func ( *byte) ( int) { return UintptrUsableSize(uintptr(unsafe.Pointer())) } // UnsafeCalloc is like Calloc except it returns an unsafe.Pointer. func ( *Allocator) ( int) ( unsafe.Pointer, error) { , := .UintptrCalloc() if != nil { return nil, } return unsafe.Pointer(), nil } // UnsafeFree is like Free except its argument is an unsafe.Pointer, which must // have been acquired from UnsafeCalloc or UnsafeMalloc or UnsafeRealloc. func ( *Allocator) ( unsafe.Pointer) ( error) { return .UintptrFree(uintptr()) } // UnsafeMalloc is like Malloc except it returns an unsafe.Pointer. func ( *Allocator) ( int) ( unsafe.Pointer, error) { , := .UintptrMalloc() if != nil { return nil, } return unsafe.Pointer(), nil } // UnsafeRealloc is like Realloc except its first argument is an // unsafe.Pointer, which must have been returned from UnsafeCalloc, // UnsafeMalloc or UnsafeRealloc. func ( *Allocator) ( unsafe.Pointer, int) ( unsafe.Pointer, error) { , := .UintptrRealloc(uintptr(), ) if != nil { return nil, } return unsafe.Pointer(), nil } // UnsafeUsableSize is like UsableSize except its argument is an // unsafe.Pointer, which must have been returned from UnsafeCalloc, // UnsafeMalloc or UnsafeRealloc. func ( unsafe.Pointer) ( int) { return UintptrUsableSize(uintptr()) }