[golang] スライス式で絞ったスライスへの参照が残る限り、除外された部分のスライスのデータはGCされない

# 目的
スライス式で絞ったスライスは、スライスへの参照が残る限り、除外されたスライスのデータもGCされないということを理解する。

# 検証
```
~/praprago$ go version
go version go1.23.2 linux/amd64
```

## (1) スライス式
``` main.go
package main

import (
	"fmt"
	"runtime"
)

// go run .
func main() {
	CallPraSliceMemory1()

	fmt.Println("CCCCCCCCCC_111222")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()
}

```
``` pra_slice_memory.go
package main

import (
	"fmt"
	"runtime"
)

func PrintAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	println("Alloc:", m.Alloc/1024, "KB")
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (1)
// ヒープに置かれたスライスデータは、参照が切れたら、GCの対象になる。
// myslice[:10] のような書き方をスライス式と呼ぶことにする。
// スライス式で取得したスライスは、コピーではなくて、スライスのデータ部分はオリジナルと同じメモリ領域を参照している。
// スライス式で取得したスライスを、返り値として別の関数などに渡すなどして、参照を切らずに使い続けると、スライス式で絞った部分のデータだけではなくて、元のスライス全体のデータもGCの対象にならない。
// まあでも、少しの時間、余計なデータにメモリを占有されてしまうだけで、参照が切れた時点でGCの対象にはなるので、特に気にする必要はないと思う。
// ただ、スライス式で絞ったスライスの寿命が長い場合は、余計なデータによるメモリ占有が起き続けるということで、メモリリークの原因になりうるので、注意が必要。
//
// 解決策は、コピーして別の関数に渡すか、スライス式で絞った部分以外のいらないデータに nil を代入してしまうことである。
//
//go:noinline
func praSliceMemory1() []byte {
	fmt.Println("BBBBBBBBBBB_111222")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	myslice := make([]byte, 1024*1024)
	for i := 0; i < len(myslice); i++ {
		myslice[i] = byte(i)
	}

	fmt.Println("BBBBBBBBBBB_222333")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	myslice2 := myslice[0:10]

	fmt.Println("BBBBBBBBBBB_333444")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	return myslice2
}

func CallPraSliceMemory1() {
	sl := praSliceMemory1()

	fmt.Println("AAAAAAAAAA_111222")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	fmt.Printf("sl: %v\n", sl) // ここまでスライスのデータへの参照が残る。

	fmt.Println("AAAAAAAAAA_222333")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()
}

```
```
~/praprago$ go run .
BBBBBBBBBBB_111222
Alloc: 46 KB
Alloc: 46 KB
BBBBBBBBBBB_222333
Alloc: 1071 KB
Alloc: 1071 KB
BBBBBBBBBBB_333444
Alloc: 1071 KB
Alloc: 1071 KB
AAAAAAAAAA_111222
Alloc: 1071 KB
Alloc: 1071 KB
sl: [0 1 2 3 4 5 6 7 8 9]
AAAAAAAAAA_222333
Alloc: 1071 KB
Alloc: 47 KB
CCCCCCCCCC_111222
Alloc: 47 KB
Alloc: 47 KB
```

## (2) 完全スライス式

``` pra_slice_memory.go
func praSliceMemory1() []byte {
...
	// 完全スライス式にしても結果は同じだった。
	myslice2 := myslice[0:10:10]
	// myslice2 := myslice[0:10]
...
}
```
```
~/praprago$ go run .
BBBBBBBBBBB_111222
Alloc: 46 KB
Alloc: 46 KB
BBBBBBBBBBB_222333
Alloc: 1071 KB
Alloc: 1071 KB
BBBBBBBBBBB_333444
Alloc: 1071 KB
Alloc: 1071 KB
AAAAAAAAAA_111222
Alloc: 1071 KB
Alloc: 1071 KB
sl: [0 1 2 3 4 5 6 7 8 9]
AAAAAAAAAA_222333
Alloc: 1071 KB
Alloc: 47 KB
CCCCCCCCCC_111222
Alloc: 47 KB
Alloc: 47 KB
```

## (3) `copy` を使う

``` pra_slice_memory.go
package main

import (
	"fmt"
	"runtime"
)

func PrintAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	println("Alloc:", m.Alloc/1024, "KB")
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (1)
// ヒープに置かれたスライスデータは、参照が切れたら、GCの対象になる。
// myslice[:10] のような書き方をスライス式と呼ぶことにする。
// スライス式で取得したスライスは、コピーではなくて、スライスのデータ部分はオリジナルと同じメモリ領域を参照している。
// スライス式で取得したスライスを、返り値として別の関数などに渡すなどして、参照を切らずに使い続けると、スライス式で絞った部分のデータだけではなくて、元のスライス全体のデータもGCの対象にならない。
// まあでも、少しの時間、余計なデータにメモリを占有されてしまうだけで、参照が切れた時点でGCの対象にはなるので、特に気にする必要はないと思う。
// ただ、スライス式で絞ったスライスの寿命が長い場合は、余計なデータによるメモリ占有が起き続けるということで、メモリリークの原因になりうるので、注意が必要。
//
// 解決策は、コピーして別の関数に渡すか、スライス式で絞った部分以外のいらないデータに nil を代入してしまうことである。
//
//go:noinline
func praSliceMemory1() []byte {
	fmt.Println("BBBBBBBBBBB_111222")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	myslice := make([]byte, 1024*1024)
	for i := 0; i < len(myslice); i++ {
		myslice[i] = byte(i)
	}

	fmt.Println("BBBBBBBBBBB_222333")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	myslice2 := make([]byte, 10)
	copy(myslice2, myslice[0:10])

 	// これ以降、myslice への参照はない。
	// ゆえに、GC が起きたタイミングで、myslice のデータは解放される。

	fmt.Println("BBBBBBBBBBB_333444")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	return myslice2
}

func CallPraSliceMemory1() {
	sl := praSliceMemory1()

	fmt.Println("AAAAAAAAAA_111222")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	fmt.Printf("sl: %v\n", sl) // ここまでスライスのデータへの参照が残る。

	fmt.Println("AAAAAAAAAA_222333")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()
}

```
```
~/praprago$ go run .
BBBBBBBBBBB_111222
Alloc: 46 KB
Alloc: 46 KB
BBBBBBBBBBB_222333
Alloc: 1071 KB
Alloc: 1071 KB
BBBBBBBBBBB_333444
Alloc: 1076 KB
Alloc: 51 KB
AAAAAAAAAA_111222
Alloc: 52 KB
Alloc: 51 KB
sl: [0 1 2 3 4 5 6 7 8 9]
AAAAAAAAAA_222333
Alloc: 52 KB
Alloc: 51 KB
CCCCCCCCCC_111222
Alloc: 52 KB
Alloc: 51 KB
```

## (4) nil を代入する

``` pra_slice_memory.go
package main

import (
	"fmt"
	"runtime"
)

func PrintAlloc() {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	println("Alloc:", m.Alloc/1024, "KB")
}

type MyStruct struct {
	myField []byte
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (1)
// ヒープに置かれたスライスデータは、参照が切れたら、GCの対象になる。
// myslice[:10] のような書き方をスライス式と呼ぶことにする。
// スライス式で取得したスライスは、コピーではなくて、スライスのデータ部分はオリジナルと同じメモリ領域を参照している。
// スライス式で取得したスライスを、返り値として別の関数などに渡すなどして、参照を切らずに使い続けると、スライス式で絞った部分のデータだけではなくて、元のスライス全体のデータもGCの対象にならない。
// まあでも、少しの時間、余計なデータにメモリを占有されてしまうだけで、参照が切れた時点でGCの対象にはなるので、特に気にする必要はないと思う。
// ただ、スライス式で絞ったスライスの寿命が長い場合は、余計なデータによるメモリ占有が起き続けるということで、メモリリークの原因になりうるので、注意が必要。
//
// 解決策は、コピーして別の関数に渡すか、スライス式で絞った部分以外のいらないデータに nil を代入してしまうことである。
//
//go:noinline
func praSliceMemory1() []MyStruct {
	fmt.Println("BBBBBBBBBBB_111222")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	// myslice のスライスのデータ部分は合計で 3 * 1000 * 1000 = 3000 KB になる。
	myslice := make([]MyStruct, 1000*1000)
	for i := 0; i < len(myslice); i++ {
		myslice[i] = MyStruct{myField: []byte{byte(i), byte(i + 1), byte(i + 2)}} // 3 bytes
	}

	fmt.Println("BBBBBBBBBBB_222333")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	myslice2 := myslice[0:10]
	// 除外されたスライスデータを nil にすることで、GCの対象になるようにする。
	// nil にしたデータは 3 * 1000 * 1000 - 3 * 10 であり、およそ 3000 KB である。
	for i := 10; i < len(myslice); i++ {
		myslice[i].myField = []byte(nil)
	}

	// ここのGCで、nil にした部分のデータが解放される。
	// nil にしたデータ約 3000 KB が解放されるはず。
	fmt.Println("BBBBBBBBBBB_333444")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	return myslice2
}

func CallPraSliceMemory1() {
	sl := praSliceMemory1()

	fmt.Println("AAAAAAAAAA_111222")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()

	fmt.Printf("sl: %v\n", sl) // ここまでスライスのデータへの参照が残る。

	fmt.Println("AAAAAAAAAA_222333")
	PrintAlloc()
	runtime.GC()
	PrintAlloc()
}

```
```
~/praprago$ go run .
BBBBBBBBBBB_111222
Alloc: 46 KB
Alloc: 46 KB
BBBBBBBBBBB_222333
Alloc: 26612 KB
Alloc: 26612 KB
BBBBBBBBBBB_333444
Alloc: 26612 KB
Alloc: 23487 KB
AAAAAAAAAA_111222
Alloc: 23487 KB
Alloc: 23487 KB
sl: [{[0 1 2]} {[1 2 3]} {[2 3 4]} {[3 4 5]} {[4 5 6]} {[5 6 7]} {[6 7 8]} {[7 8 9]} {[8 9 10]} {[9 10 11]}]
AAAAAAAAAA_222333
Alloc: 23488 KB
Alloc: 47 KB
CCCCCCCCCC_111222
Alloc: 47 KB
Alloc: 47 KB
```

# 参考

https://www.amazon.co.jp/-/en/Teiva-Harsanyi/dp/4295017531