3

I want to implement an efficient LRU cache that automatically evicts items based on free memory.

Right now only 2 things come to mind:

  • Poll with gosigar
  • or set a fixed maximum and periodically check runtime.ReadMemStats.

Are there any other ways? How does memcached do it?

  • 1
    In memcached, you specify the amount of memory to use, it doesn't adapt to the OS's or Process' memory capacity or requirements. – thwd Nov 15 '13 at 14:41
  • I kind of like user-tuned cache size like memcached (and [groupcache](https://github.com/golang/groupcache), which is in Go). If you want some adaptive strategy, maybe let the user specify size in terms of percent of _total_ RAM, and default to something safe like 1%. Free RAM is a funny number in Linux, because the kernel will try to use RAM for buffers/cache to speed up I/O, and it varies based on what the system's workload is this instant. Relying on it could make your cache's behavior and effect on the box it runs on a little unpredictable. – twotwotwo Nov 17 '13 at 00:57
  • 1% of total ram? That would be like 1000 objects the cache could hold on a 1GB machine. Maybe I should have mentioned that this is not a separate binary, but an app embedded in-memory cache. –  Nov 17 '13 at 12:26

1 Answers1

1

I implemented it polling the system memory statistics every 1 second.

See: https://github.com/eaigner/last

Read memory stats on Linux:

import (
    "syscall"
)

func ReadSysMemStats(s *MemStats) error {
    if s == nil {
        return nil
    }
    var info syscall.Sysinfo_t
    err := syscall.Sysinfo(&info)
    if err != nil {
        return err
    }

    s.Total = info.Totalram
    s.Free = info.Freeram
    s.Used = s.Total - s.Free

    return nil
}

And on Darwin/OSX

/*
#include <mach/mach.h>
#include <mach/mach_host.h>
*/
import "C"

import (
    "fmt"
    "unsafe"
)

func readSysMemStats(s *SysMemStats) error {
    if s == nil {
        return nil
    }
    var vm_pagesize C.vm_size_t
    var vm_stat C.vm_statistics_data_t
    var count C.mach_msg_type_number_t = C.HOST_VM_INFO_COUNT

    host_port := C.host_t(C.mach_host_self())

    C.host_page_size(host_port, &vm_pagesize)

    status := C.host_statistics(
        host_port,
        C.HOST_VM_INFO,
        C.host_info_t(unsafe.Pointer(&vm_stat)),
        &count)

    if status != C.KERN_SUCCESS {
        return fmt.Errorf("could not get vm statistics: %d", status)
    }

    // Stats in bytes
    free := uint64(vm_stat.free_count)
    active := uint64(vm_stat.active_count)
    inactive := uint64(vm_stat.inactive_count)
    wired := uint64(vm_stat.wire_count)
    pagesize := uint64(vm_pagesize)

    s.Used = (active + inactive + wired) * pagesize
    s.Free = free * pagesize
    s.Total = s.Used + s.Free

    return nil
}