Skip to content

Instantly share code, notes, and snippets.

@timruffles
Last active January 17, 2025 14:03
Show Gist options
  • Save timruffles/48bfa3bd5a625d80e7c73d0eacfc8335 to your computer and use it in GitHub Desktop.
Save timruffles/48bfa3bd5a625d80e7c73d0eacfc8335 to your computer and use it in GitHub Desktop.
golang - demonstrates the risk of using runtime.SetFinalizer to 'manage' C memory. Since C heap is invisible to Go GC, without sufficient Go memory pressure the finalizers won't run, and your process can hog a lot of memory. Suggestion: add a manual Close() method to your CGO bindings, so your library users can get predictable memory use.
package main
import "C"
import (
"fmt"
"runtime"
"sync"
"syscall"
"unsafe"
)
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int call_count = 0;
char* create_string(int alloc_size)
{
call_count++;
char* str = (char*)malloc(alloc_size * sizeof(char));
if (str == NULL) {
fprintf(stderr, "Memory allocation failed\n");
exit(1);
}
snprintf(str, alloc_size, "Hello CGO! Call %d", call_count);
return str;
}
*/
import "C"
const (
kib = 1024
mib = kib * 1024
)
const (
loopSize = 500
allocPerIteration = mib * 2
sampleSize = 100
// whether it should force a Golang GC every sample
forceGc = false
)
var (
rUsage syscall.Rusage
allocs int
frees int
lock sync.Mutex
)
func main() {
for sample := 1; sample <= sampleSize; sample++ {
for i := 0; i < loopSize; i++ {
a := NewAllocator()
if i == 0 && sample == 1 {
str := C.GoString(a.memory)
fmt.Println("test call: ", str)
fmt.Printf("sample\trss_mb\tallocs\tfrees\n")
}
}
if forceGc {
runtime.GC()
}
err := syscall.Getrusage(syscall.RUSAGE_SELF, &rUsage)
if err != nil {
panic(err)
}
lock.Lock()
// it's in bytes in OSX
mibUsed := rUsage.Maxrss / mib
fmt.Printf("%d\t%dMiB\t%d\t%d\n", sample, mibUsed, allocs, frees)
lock.Unlock()
}
}
type Allocator struct {
memory *C.char
}
func NewAllocator() *Allocator {
a := &Allocator{}
runtime.SetFinalizer(a, free)
a.memory = C.create_string(allocPerIteration)
allocs++
return a
}
func free(a *Allocator) {
C.free(unsafe.Pointer(a.memory))
a.memory = nil
frees++
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment