0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[golang] サイズが未知の場合は、ヒープに配置されることを理解する。

Posted at

目的

スライスが作成されるとき、容量が未知の場合は、ヒープに配置されることを理解する。

環境

$ go version
go version go1.23.2 linux/amd64

検証

(1) スライスの容量が既知

スライスはヒープにエスケープされていない(スタックに配置されている)ことがわかる。

main.go
package main

func main() {
	_ = make([]byte, 1, 2)
}

main_test.go
package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}

$ go build -gcflags "-m=2" .
# gopractice
./main.go:3:6: can inline main with cost 5 as: func() { _ = make([]byte, 1, 2) }
./main.go:4:10: make([]byte, 1, 2) does not escape
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: gopractice
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Benchmark-2     1000000000               0.3725 ns/op          0 B/op          0 allocs/op
PASS
ok      gopractice      0.418s
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000466be0 <main.main>:
  466be0:	c3                   	ret
  466be1:	cc                   	int3
  466be2:	cc                   	int3
  466be3:	cc                   	int3
  466be4:	cc                   	int3
  466be5:	cc                   	int3
  466be6:	cc                   	int3
  466be7:	cc                   	int3
  466be8:	cc                   	int3
  466be9:	cc                   	int3
  466bea:	cc                   	int3
  466beb:	cc                   	int3
  466bec:	cc                   	int3
  466bed:	cc                   	int3
  466bee:	cc                   	int3
  466bef:	cc                   	int3
  466bf0:	cc                   	int3
  466bf1:	cc                   	int3
  466bf2:	cc                   	int3
  466bf3:	cc                   	int3
  466bf4:	cc                   	int3
  466bf5:	cc                   	int3
  466bf6:	cc                   	int3
  466bf7:	cc                   	int3
  466bf8:	cc                   	int3
  466bf9:	cc                   	int3
  466bfa:	cc                   	int3
  466bfb:	cc                   	int3
  466bfc:	cc                   	int3
  466bfd:	cc                   	int3
  466bfe:	cc                   	int3
  466bff:	cc                   	int3

(2) スライスの容量が未知

スライスはヒープにエスケープされていることがわかる。

main.go
package main

func main() {
	n := 2
	_ = make([]byte, 1, n)
}

main_test.go
package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}

$ go build -gcflags "-m=2" .
# gopractice
./main.go:3:6: can inline main with cost 10 as: func() { n := 2; _ = make([]byte, 1, n) }
./main.go:5:10: make([]byte, 1, n) escapes to heap:
./main.go:5:10:   flow: {heap} = &{storage for make([]byte, 1, n)}:
./main.go:5:10:     from make([]byte, 1, n) (non-constant size) at ./main.go:5:10
./main.go:5:10: make([]byte, 1, n) escapes to heap
$ go test -bench=. -benchmem
goos: linux
goarch: amd64
pkg: gopractice
cpu: Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Benchmark-2     88923357                12.78 ns/op            2 B/op          1 allocs/op
PASS
ok      gopractice      1.156s
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000466be0 <main.main>:
  466be0:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  466be4:	76 25                	jbe    466c0b <main.main+0x2b>
  466be6:	55                   	push   %rbp
  466be7:	48 89 e5             	mov    %rsp,%rbp
  466bea:	48 83 ec 18          	sub    $0x18,%rsp
  466bee:	48 8d 05 4b 56 00 00 	lea    0x564b(%rip),%rax        # 46c240 <type:*+0x5240>
  466bf5:	bb 01 00 00 00       	mov    $0x1,%ebx
  466bfa:	b9 02 00 00 00       	mov    $0x2,%ecx
  466bff:	90                   	nop
  466c00:	e8 5b 79 ff ff       	call   45e560 <runtime.makeslice>
  466c05:	48 83 c4 18          	add    $0x18,%rsp
  466c09:	5d                   	pop    %rbp
  466c0a:	c3                   	ret
  466c0b:	e8 10 b1 ff ff       	call   461d20 <runtime.morestack_noctxt.abi0>
  466c10:	eb ce                	jmp    466be0 <main.main>
  466c12:	cc                   	int3
  466c13:	cc                   	int3
  466c14:	cc                   	int3
  466c15:	cc                   	int3
  466c16:	cc                   	int3
  466c17:	cc                   	int3
  466c18:	cc                   	int3
  466c19:	cc                   	int3
  466c1a:	cc                   	int3
  466c1b:	cc                   	int3
  466c1c:	cc                   	int3
  466c1d:	cc                   	int3
  466c1e:	cc                   	int3
  466c1f:	cc                   	int3

(3)

スライスの容量が既知でも、そのスタックフレーム以外に引き渡されるならば、ヒープにエスケープされる。
2 allocs/opを見ると、二回のヒープへのエスケープが発生している。fmt.Printf("%p\n", &s) は、スライス構造体のアドレスを引き渡しており、これにより、スライス構造体のエスケープと、スライス要素のエスケープが発生するので、二回のエスケープが発生している。

main.go
package main

import "fmt"

func main() {
	s := make([]byte, 1, 17)
	fmt.Printf("%p\n", &s)
	fmt.Printf("%p\n", &s[0])
}

main_test.go
package main

import "testing"

func Benchmark(b *testing.B) {
	for i := 0; i < b.N; i++ {
		main()
	}
}

$ go build -gcflags "-m=2" .
# gopractice
./main.go:5:6: cannot inline main: function too complex: cost 171 exceeds budget 80
./main.go:7:12: inlining call to fmt.Printf
./main.go:8:12: inlining call to fmt.Printf
./main.go:6:11: make([]byte, 1, 17) escapes to heap:
./main.go:6:11:   flow: s = &{storage for make([]byte, 1, 17)}:
./main.go:6:11:     from make([]byte, 1, 17) (spill) at ./main.go:6:11
./main.go:6:11:     from s := make([]byte, 1, 17) (assign) at ./main.go:6:4
./main.go:6:11:   flow: {storage for ... argument} = s:
./main.go:6:11:     from s[0] (dot of pointer) at ./main.go:8:23
./main.go:6:11:     from &s[0] (address-of) at ./main.go:8:21
./main.go:6:11:     from &s[0] (interface-converted) at ./main.go:8:21
./main.go:6:11:     from ... argument (slice-literal-element) at ./main.go:8:12
./main.go:6:11:   flow: fmt.a = &{storage for ... argument}:
./main.go:6:11:     from ... argument (spill) at ./main.go:8:12
./main.go:6:11:     from fmt.format, fmt.a := "%p\n", ... argument (assign-pair) at ./main.go:8:12
./main.go:6:11:   flow: {heap} = *fmt.a:
./main.go:6:11:     from fmt.Fprintf(os.Stdout, fmt.format, fmt.a...) (call parameter) at ./main.go:8:12
./main.go:6:2: s escapes to heap:
./main.go:6:2:   flow: {storage for ... argument} = &s:
./main.go:6:2:     from &s (address-of) at ./main.go:7:21
./main.go:6:2:     from &s (interface-converted) at ./main.go:7:21
./main.go:6:2:     from ... argument (slice-literal-element) at ./main.go:7:12
./main.go:6:2:   flow: fmt.a = &{storage for ... argument}:
./main.go:6:2:     from ... argument (spill) at ./main.go:7:12
./main.go:6:2:     from fmt.format, fmt.a := "%p\n", ... argument (assign-pair) at ./main.go:7:12
./main.go:6:2:   flow: {heap} = *fmt.a:
./main.go:6:2:     from fmt.Fprintf(os.Stdout, fmt.format, fmt.a...) (call parameter) at ./main.go:7:12
./main.go:6:2: moved to heap: s
./main.go:6:11: make([]byte, 1, 17) escapes to heap
./main.go:7:12: ... argument does not escape
./main.go:8:12: ... argument does not escape
$ go test -bench=. -benchmem
...
0xc000250d08
0xc00024ef18
0xc000250d20
0xc00024ef30
0xc000250d38
0xc00024ef48
   62806             18745 ns/op              48 B/op          2 allocs/op
PASS
ok      gopractice      1.376s
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
0000000000490880 <main.main>:
  490880:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  490884:	0f 86 fc 00 00 00    	jbe    490986 <main.main+0x106>
  49088a:	55                   	push   %rbp
  49088b:	48 89 e5             	mov    %rsp,%rbp
  49088e:	48 83 ec 60          	sub    $0x60,%rsp
  490892:	48 8d 05 27 80 00 00 	lea    0x8027(%rip),%rax        # 4988c0 <type:*+0x78c0>
  490899:	e8 e2 bf f7 ff       	call   40c880 <runtime.newobject>
  49089e:	48 89 44 24 58       	mov    %rax,0x58(%rsp)
  4908a3:	bb 01 00 00 00       	mov    $0x1,%ebx
  4908a8:	b9 11 00 00 00       	mov    $0x11,%ecx
  4908ad:	48 8d 05 cc 8e 00 00 	lea    0x8ecc(%rip),%rax        # 499780 <type:*+0x8780>
  4908b4:	e8 c7 5a fd ff       	call   466380 <runtime.makeslice>
  4908b9:	48 8b 4c 24 58       	mov    0x58(%rsp),%rcx
  4908be:	48 c7 41 08 01 00 00 	movq   $0x1,0x8(%rcx)
  4908c5:	00 
  4908c6:	48 c7 41 10 11 00 00 	movq   $0x11,0x10(%rcx)
  4908cd:	00 
  4908ce:	83 3d fb 3c 0e 00 00 	cmpl   $0x0,0xe3cfb(%rip)        # 5745d0 <runtime.writeBarrier>
  4908d5:	74 0f                	je     4908e6 <main.main+0x66>
  4908d7:	e8 84 b0 fd ff       	call   46b960 <runtime.gcWriteBarrier2>
  4908dc:	49 89 03             	mov    %rax,(%r11)
  4908df:	48 8b 11             	mov    (%rcx),%rdx
  4908e2:	49 89 53 08          	mov    %rdx,0x8(%r11)
  4908e6:	48 89 01             	mov    %rax,(%rcx)
  4908e9:	48 8d 15 10 7c 00 00 	lea    0x7c10(%rip),%rdx        # 498500 <type:*+0x7500>
  4908f0:	48 89 54 24 48       	mov    %rdx,0x48(%rsp)
  4908f5:	48 89 4c 24 50       	mov    %rcx,0x50(%rsp)
  4908fa:	48 8b 1d 67 3b 0c 00 	mov    0xc3b67(%rip),%rbx        # 554468 <os.Stdout>
  490901:	48 8d 05 f0 4f 04 00 	lea    0x44ff0(%rip),%rax        # 4d58f8 <go:itab.*os.File,io.Writer>
  490908:	bf 03 00 00 00       	mov    $0x3,%edi
  49090d:	48 8d 74 24 48       	lea    0x48(%rsp),%rsi
  490912:	41 b8 01 00 00 00    	mov    $0x1,%r8d
  490918:	4d 89 c1             	mov    %r8,%r9
  49091b:	48 8d 0d 82 f6 01 00 	lea    0x1f682(%rip),%rcx        # 4affa4 <go:string.*+0x4c>
  490922:	e8 19 98 ff ff       	call   48a140 <fmt.Fprintf>
  490927:	48 8b 54 24 58       	mov    0x58(%rsp),%rdx
  49092c:	48 83 7a 08 00       	cmpq   $0x0,0x8(%rdx)
  490931:	76 47                	jbe    49097a <main.main+0xfa>
  490933:	48 8b 12             	mov    (%rdx),%rdx
  490936:	4c 8d 15 43 64 00 00 	lea    0x6443(%rip),%r10        # 496d80 <type:*+0x5d80>
  49093d:	4c 89 54 24 38       	mov    %r10,0x38(%rsp)
  490942:	48 89 54 24 40       	mov    %rdx,0x40(%rsp)
  490947:	48 8b 1d 1a 3b 0c 00 	mov    0xc3b1a(%rip),%rbx        # 554468 <os.Stdout>
  49094e:	48 8d 05 a3 4f 04 00 	lea    0x44fa3(%rip),%rax        # 4d58f8 <go:itab.*os.File,io.Writer>
  490955:	48 8d 0d 48 f6 01 00 	lea    0x1f648(%rip),%rcx        # 4affa4 <go:string.*+0x4c>
  49095c:	bf 03 00 00 00       	mov    $0x3,%edi
  490961:	48 8d 74 24 38       	lea    0x38(%rsp),%rsi
  490966:	41 b8 01 00 00 00    	mov    $0x1,%r8d
  49096c:	4d 89 c1             	mov    %r8,%r9
  49096f:	e8 cc 97 ff ff       	call   48a140 <fmt.Fprintf>
  490974:	48 83 c4 60          	add    $0x60,%rsp
  490978:	5d                   	pop    %rbp
  490979:	c3                   	ret
  49097a:	31 c0                	xor    %eax,%eax
  49097c:	48 89 c1             	mov    %rax,%rcx
  49097f:	90                   	nop
  490980:	e8 7b b3 fd ff       	call   46bd00 <runtime.panicIndex>
  490985:	90                   	nop
  490986:	e8 55 92 fd ff       	call   469be0 <runtime.morestack_noctxt.abi0>
  49098b:	e9 f0 fe ff ff       	jmp    490880 <main.main>
$ go run main.go
0xc00009e000
0xc0000a0000

参考

例えば、s := make(int[], 10)はヒープにエスケープされないかもしれませんが、s := make([]int, n)はそのサイズが変数に基づいているのでヒープにエスケープされます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?