mem
Measure a program's memory use, force garbage collection, write pprof profiles and serve live profiling data.
import "std:mem"std:mem shows what a program is doing with memory. Reach for it when a long-running program grows, when you want to know what a data structure costs, or when you need a profile to find where the allocations come from. OSL frees memory for you with a garbage collector, so most programs never need this package. It is for the ones that do.
Exact numbers differ between machines and runs, so the examples print comparisons, not the figures themselves.
Memory statistics#
mem.stats() returns a snapshot. The fields you will use most are alloc (bytes in use now), totalAlloc (bytes ever allocated), heapObjects (live objects), numGC (collections so far) and goroutines (running threads):
import "std:mem"
auto now = mem.stats()
log now.alloc > 0
log now.heapObjects > 0
log now.goroutines >= 1
log now.sys >= now.allocAll sizes are in bytes. sys is what the program has asked the operating system for, which is always at least what is in use.
Measuring what something costs#
Take a snapshot before and after, and compare. mem.gc() runs a collection first and then returns fresh statistics, so garbage from earlier work doesn't blur the reading:
import "std:mem"
auto before = mem.gc()
int[] numbers = []
for i in 1 to 200000 (
numbers.append(i)
)
auto after = mem.gc()
log numbers.len
log after.alloc > before.alloc
log after.numGC > before.numGC
log (after.alloc - before.alloc) > 1000000The array holds 200000 numbers, so it takes well over a million bytes. Keep a reference to the data until after the second reading, or the collector may free it before you measure.
Watching for growth#
A leak is memory that keeps growing because something still refers to it. Measure after a collection, at intervals, and look at the trend, not one number:
import "std:mem"
string[] history = []
def remember(string line) (
history.append(line)
)
auto start = mem.gc()
for round in 1 to 3 (
for i in 1 to 50000 (
remember("entry " ++ i)
)
auto now = mem.gc()
log "round" round "grew:" now.alloc > start.alloc
)
log history.lenA program that logs "grew: true" forever when it should be steady is keeping something it doesn't need, such as a list or a cache with no limit. cache has a capacity for this reason.
Writing a profile#
mem.dump(path) writes a pprof profile, which Go's go tool pprof can open to show where memory was allocated. The default is a heap profile, and the profile option picks others: allocs, goroutine, threadcreate, block or mutex:
import "std:mem"
import "std:fs"
fs.remove("heap.pprof", {force: true}).unwrap()
mem.dump("heap.pprof").unwrap()
log fs.size("heap.pprof").unwrap() > 0
mem.dump("threads.pprof", {profile: "goroutine"}).unwrap()
log fs.exists("threads.pprof")
log mem.dump("no-such-folder/heap.pprof").unwrapErr().kind
fs.remove("heap.pprof", {force: true}).unwrap()
fs.remove("threads.pprof", {force: true}).unwrap()The file is replaced atomically, so a profile is never half written. Then, in a shell, go tool pprof heap.pprof opens it. A heap profile samples allocations, so call mem.setProfileRate early, before the allocations you want to see, to change how often it samples.
Live profiling#
mem.serve(address) starts a profiling server in the background and returns a handle. Profiles are under /debug/pprof/, where go tool pprof http://host/debug/pprof/heap can read them from a running program. Use 127.0.0.1 so only this machine can reach it, and port 0 for a free one:
import "std:mem"
import "std:requests"
auto profiler = mem.serve("127.0.0.1:0").unwrap()
log profiler.address().startsWith("127.0.0.1:")
string url = "http://" ++ profiler.address() ++ "/debug/pprof/goroutine?debug=1"
auto reply = requests.get(url).unwrap()
log reply.status reply.body.startsWith("goroutine profile")
profiler.close()Tuning the collector#
Two settings change how eagerly the collector runs, and both return the previous value so you can put it back. setGCPercent is how far the heap may grow past the live data before the next collection (100 by default), and setMemoryLimit is a soft limit in bytes where the collector works harder near the limit. A limit of 0 means none:
import "std:mem"
int old = mem.setGCPercent(200)
log old
log mem.setGCPercent(old)
log mem.setMemoryLimit(512 * 1024 * 1024)
log mem.setMemoryLimit(0)
A higher percentage collects less often and uses more memory, which suits a program with a small live heap that allocates a lot. A negative limit or profile rate throws a ValueError.
Why does the page call mem.gc() before taking a measurement?
Did it grow?
Measure alloc after a collection, build a list of 100000 numbers, measure again after a collection, and print whether it grew. Keep using the list after the second reading.
true 100000
API reference#
13 members, generated from the std:mem source of OSL 9.3.0.
Functions
mem.address() stringDeprecated. Deprecated: use the Server from mem.serve(address) and server.address() instead.
address returns the deprecated package-level server's address, or "".
mem.dump(string path, object? options) result<unit, error>dump writes a pprof profile to path, replacing the file atomically, for go tool pprof. Options: profile (default "heap") names the profile: "heap", "allocs", "goroutine", "threadcreate", "block" or "mutex". A heap or allocs profile runs a garbage collection first. Fails with NotFoundError when the directory doesn't exist, PermissionError or IOError. Throws ValueError for an unknown profile.
mem.dumpProfile(any name, any path) booleanDeprecated. Deprecated: use mem.dump(path, {profile: name}) instead.
dumpProfile writes a named pprof profile and reports whether it worked.
mem.gc() {alloc: int, frees: int, gcCPUFraction: number, gcSys: int, goroutines: int, heapAlloc: int, heapIdle: int, heapInuse: int, heapObjects: int, heapReleased: int, heapSys: int, liveObjects: int, mallocs: int, mcacheInuse: int, mspanInuse: int, nextGC: int, numGC: int, otherSys: int, pauseTotalNs: int, profileRate: int, stackInuse: int, stackSys: int, sys: int, totalAlloc: int}gc runs a garbage collection and then returns mem.stats().
mem.profileRate() intprofileRate returns how many bytes are allocated, on average, between samples in the heap profile; 0 means heap profiling is off.
mem.serve(string address) result<*mem.Server, error>serve starts a pprof HTTP server in the background on address (such as "127.0.0.1:6060", or ":0" for any free port) and returns its Server handle; profiles are under /debug/pprof/. Anyone who can reach the address can read them, so prefer a loopback address. Fails with NetworkError when the address can't be listened on.
mem.setGCPercent(int percent) intsetGCPercent sets how much the heap may grow past the live data from the last garbage collection before the next one starts, as a percentage (Go's GOGC), and returns the previous setting. The default is 100, or the GOGC environment variable. A larger value collects less often and uses more memory, which suits a program with a small live heap that allocates a lot. A negative value turns garbage collection off, though mem.setMemoryLimit can still trigger it.
mem.setMemoryLimit(int bytes) intsetMemoryLimit sets a soft limit in bytes on the memory the Go runtime uses (Go's GOMEMLIMIT), collecting garbage more often as it nears the limit, and returns the previous limit. Pass 0 for no limit; a previous value of 0 means there was none, so the result can be passed back to restore it. Throws ValueError for a negative limit.
mem.setProfileRate(int bytes) intsetProfileRate sets the heap profile sampling rate in bytes (0 turns it off; 1 records every allocation) and returns the previous rate. Set it early, before the allocations to profile. Throws ValueError for a negative rate.
mem.stats() {alloc: int, frees: int, gcCPUFraction: number, gcSys: int, goroutines: int, heapAlloc: int, heapIdle: int, heapInuse: int, heapObjects: int, heapReleased: int, heapSys: int, liveObjects: int, mallocs: int, mcacheInuse: int, mspanInuse: int, nextGC: int, numGC: int, otherSys: int, pauseTotalNs: int, profileRate: int, stackInuse: int, stackSys: int, sys: int, totalAlloc: int}stats returns a snapshot of memory statistics with the keys alloc, totalAlloc, sys, mallocs, frees, liveObjects, heapAlloc, heapSys, heapIdle, heapInuse, heapReleased, heapObjects, stackInuse, stackSys, mspanInuse, mcacheInuse, gcSys, otherSys, nextGC (sizes in bytes), numGC, pauseTotalNs (nanoseconds), gcCPUFraction (0 to 1), goroutines and profileRate.
mem.stop() booleanDeprecated. Deprecated: use the Server from mem.serve(address) and server.close() instead.
stop stops the server started by the deprecated package-level serve and reports whether one was running.