释放结构图

I am working with a very large map of pointer to struct. It is growing over the lifetime of the program (I use it as a buffer) and I wrote a function that is supposed to reduce it size when it is called.

type S struct {
 a uint32
 b []uint32
}

s := make(map[uint32]*S)

for k, v := range s {
  delete(s, k)
  s[k] = &S{a: v.a}
}

I remove b from every element of the map, so I expected the size of the map in memory to shrink (b is a slice of length > 10). However the memory is not freed, why?

The size of the map value &S, a pointer, is the same irrespective of the capacity of slice b.

package main

import (
    "fmt"
    "unsafe"
)

type S struct {
    a uint32
    b []uint32
}

func main() {
    size := unsafe.Sizeof(&S{})
    fmt.Println(size)

    size = unsafe.Sizeof(&S{b: make([]uint32, 10)})
    fmt.Println(size)

    s := make(map[uint32]*S)

    for k, v := range s {
        delete(s, k)
        s[k] = &S{a: v.a}
    }
}

Output:

8
8

A slice is represented internally by

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

When you set &S{a: v.a} you set b to initial values: array to nil and len and cap to zero. The memory formerly occupied by the underlying array is returned to the garbage collector for reuse.

The map size is bounded to the maximum size it had at any point. Because you store pointers (map[uint32]*S) and not values the deleted objects will get garbage collected eventually but usually not immediately and that why you don’t see it in top/htop like monitors.

The runtime is clever enough and reserves memory for future use if the system is not under pressure or low on resources.

See https://stackoverflow.com/a/49963553/1199408 to understand more about memory.

In your example you don't need to call delete. You will achieve what you want just by clearing the slice in the struct.

type S struct {
 a uint32
 b []uint32
}

s := make(map[uint32]*S)

for k, v := range s {
  v.b = []uint32{}
}