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

目的

インターフェースが確定でヒープに割り当てられるというルールは特にないということを理解する。
下記の記事で、インターフェースは確定でヒープに配置されるという旨の記述があったので検証してみた。かなり古い記事なので、昔の golang はそういう仕様だったということかもしれない。

環境

$ go version
go version go1.23.2 linux/amd64

検証

./main.go:16:18: &Duck{} does not escape より、インターフェースはスタックに配置されていることがわかる。
つまり、検証対象の記事の主張は、現在のバージョンでは間違っているということである。

package main

import "fmt"

type Sounder interface {
	Sound()
}

type Duck struct{}

func (d *Duck) Sound() {
	fmt.Println("quack")
}

func main() {
	var d Sounder = &Duck{}
	d.Sound()
}

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:11:6: can inline (*Duck).Sound with cost 78 as: method(*Duck) func() { fmt.Println(... argument...) }
./main.go:15:6: can inline main with cost 67 as: func() { d := &Duck{}; d.Sound() }
./main.go:12:13: inlining call to fmt.Println
./main.go:17:9: devirtualizing d.Sound to *Duck
./main.go:17:9: inlining call to (*Duck).Sound
./main.go:17:9: inlining call to fmt.Println
./main.go:12:14: "quack" escapes to heap:
./main.go:12:14:   flow: {storage for ... argument} = &{storage for "quack"}:
./main.go:12:14:     from "quack" (spill) at ./main.go:12:14
./main.go:12:14:     from ... argument (slice-literal-element) at ./main.go:12:13
./main.go:12:14:   flow: fmt.a = &{storage for ... argument}:
./main.go:12:14:     from ... argument (spill) at ./main.go:12:13
./main.go:12:14:     from fmt.a := ... argument (assign-pair) at ./main.go:12:13
./main.go:12:14:   flow: {heap} = *fmt.a:
./main.go:12:14:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./main.go:12:13
./main.go:11:7: d does not escape
./main.go:12:13: ... argument does not escape
./main.go:12:14: "quack" escapes to heap
./main.go:17:9: "quack" escapes to heap:
./main.go:17:9:   flow: {storage for ... argument} = &{storage for "quack"}:
./main.go:17:9:     from "quack" (spill) at ./main.go:17:9
./main.go:17:9:     from ... argument (slice-literal-element) at ./main.go:17:9
./main.go:17:9:   flow: fmt.a = &{storage for ... argument}:
./main.go:17:9:     from ... argument (spill) at ./main.go:17:9
./main.go:17:9:     from fmt.a := ... argument (assign-pair) at ./main.go:17:9
./main.go:17:9:   flow: {heap} = *fmt.a:
./main.go:17:9:     from fmt.Fprintln(os.Stdout, fmt.a...) (call parameter) at ./main.go:17:9
./main.go:16:18: &Duck{} does not escape
./main.go:17:9: ... argument does not escape
./main.go:17:9: "quack" escapes to heap
<autogenerated>:1: parameter ~p0 leaks to {heap} with derefs=0:
<autogenerated>:1:   flow: {heap} = ~p0:
<autogenerated>:1:     from ~p0.Sound() (call parameter) at <autogenerated>:1
$ go test -bench=. -benchmem
...
quack
...
  320628              6311 ns/op               0 B/op          0 allocs/op
PASS
ok      gopractice      2.073s
$ go build -o main_binary main.go
$ objdump -d main_binary > objdump.txt
000000000048f140 <main.main>:
  48f140:	49 3b 66 10          	cmp    0x10(%r14),%rsp
  48f144:	76 47                	jbe    48f18d <main.main+0x4d>
  48f146:	55                   	push   %rbp
  48f147:	48 89 e5             	mov    %rsp,%rbp
  48f14a:	48 83 ec 38          	sub    $0x38,%rsp
  48f14e:	90                   	nop
  48f14f:	48 8d 15 aa 95 00 00 	lea    0x95aa(%rip),%rdx        # 498700 <type:*+0x8700>
  48f156:	48 89 54 24 28       	mov    %rdx,0x28(%rsp)
  48f15b:	48 8d 15 b6 4f 04 00 	lea    0x44fb6(%rip),%rdx        # 4d4118 <runtime.buildVersion.str+0x10>
  48f162:	48 89 54 24 30       	mov    %rdx,0x30(%rsp)
  48f167:	48 8b 1d fa 32 0c 00 	mov    0xc32fa(%rip),%rbx        # 552468 <os.Stdout>
  48f16e:	48 8d 05 e3 54 04 00 	lea    0x454e3(%rip),%rax        # 4d4658 <go:itab.*os.File,io.Writer>
  48f175:	48 8d 4c 24 28       	lea    0x28(%rsp),%rcx
  48f17a:	bf 01 00 00 00       	mov    $0x1,%edi
  48f17f:	48 89 fe             	mov    %rdi,%rsi
  48f182:	e8 b9 af ff ff       	call   48a140 <fmt.Fprintln>
  48f187:	48 83 c4 38          	add    $0x38,%rsp
  48f18b:	5d                   	pop    %rbp
  48f18c:	c3                   	ret
  48f18d:	e8 4e aa fd ff       	call   469be0 <runtime.morestack_noctxt.abi0>
  48f192:	eb ac                	jmp    48f140 <main.main>
$ go run main.go
quack

おまけ

./main.go:17:9: "quack" escapes to heap は、fmt パッケージのPrintln 関数に文字列を渡すと、文字列がヒープにエスケープされることを示している。

func Println(a ...any) (n int, err error)

文字列はほとんどがポインタなので、文字列を渡すために関数を呼び出しても、バイトの深いコピーは行われません。浅くコピーされた文字列は、依然として同じ基底配列を参照しています。

参考

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?