LINUX.ORG.RU

История изменений

Исправление thegoldone, (текущая версия) :

Возможно обратиться по указателю к переменной в Стеке метода родителя.

Вроде лежит на поверхности, а на самом деле нужно поразбираться.

Да, с этим стеком ещё куча приколов. Например цепочка:

package main

import "fmt"

type A struct {
	N     int
	Shift int
}

func (v A) WithShift(shift int) A {
	v.Shift = shift // всё равно копия, можно менять
	return v        // по месту и возвращать
}

// нет нужды копировать на стек A, используем указатель
func (v *A) Shifted() int {
	return v.N + v.Shift
}

func main() {
	var a = A{N: 10}

	fmt.Println("A shifted:", a.WithShift(20).Shifted(), "origin", a)
}

Получаем [play].

./prog.go:24:44: cannot call pointer method Shifted on A

Потому что на стеке. И указатель не даёт взять.

И мы попадаем в ситуацию или выделять, или выделять.

package main

import (
	"fmt"
	"testing"
)

type B struct {
	N     int
	Shift int
}

func (b B) WithShift(shift int) B {
	b.Shift = shift
	return b
}

func (b B) WithOffset(offset int) B {
	b.Shift += offset
	return b
}

func (b B) Shifted() int {
	return b.N + b.Shift
}

func (b B) String() string {
	return fmt.Sprintf("B{%d %+d}", b.N, b.Shift)
}

type C struct {
	N     int
	Shift int
}

func (c *C) WithShift(shift int) *C {
	return &C{N: c.N, Shift: shift}
}

func (c C) WithOffset(offset int) *C {
	return &C{N: c.N, Shift: c.Shift + offset}
}

func (c *C) Shifted() int {
	return c.N + c.Shift
}

func (c *C) String() string {
	return fmt.Sprintf("C{%d %+d}", c.N, c.Shift)
}

var (
	_bx  B
	_bxp *B
	_cx  C
	_cxp *C
	_b   string
	_c   string
)

func BenchmarkBAssign(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_bx = bx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkBAssignPtr(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		var t = bx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
		_bxp = &t
	}
}

func BenchmarkBSprintf(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_b = fmt.Sprintf("%s",
			bx.WithShift(i).
				WithOffset(15).
				WithShift(i+15).
				WithOffset(30),
		)
	}
}

func BenchmarkCAssign(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cx = *cx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkCAssignPtr(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cxp = cx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkCSprintf(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_c = fmt.Sprintf("%s",
			cx.WithShift(i).
				WithOffset(15).
				WithShift(i+15).
				WithOffset(30),
		)
	}
}

С результатом

# go test -benchmem -bench .
goos: linux
goarch: amd64
pkg: misc
cpu: AMD Ryzen 9 5950X 16-Core Processor            
BenchmarkBAssign-32       1000000000      0.4216 ns/op     0 B/op    0 allocs/op
BenchmarkBAssignPtr-32      88171006     13.26 ns/op      16 B/op    1 allocs/op
BenchmarkBSprintf-32         6228514    195.2 ns/op       56 B/op    4 allocs/op
BenchmarkCAssign-32       1000000000      0.4269 ns/op     0 B/op    0 allocs/op
BenchmarkCAssignPtr-32      79312046     13.60 ns/op      16 B/op    1 allocs/op
BenchmarkCSprintf-32         6245740    194.5 ns/op       56 B/op    4 allocs/op
PASS
ok      misc   6.045s

Во случае С мы сами выделяем новое значение. А в случае B оно выделяется во время fmt.Sprintf, и при сохранении указателя в глобальной переменной.

Причём Го оптимизирует цепочку выделений в одно (?) и итоговый результат одинаков для C и B.


P.S. ну и вишенка на торте

type Shifteder interface {
	Shifted() int
}

func shifted(sher Shifteder) int {
	return sher.Shifted()
}
var _bs, _cs int

func BenchmarkBShifted(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_bs = shifted(
			bx.WithShift(i).
				WithOffset(15).
				WithShift(i + 15).
				WithOffset(30),
		)
	}
}

func BenchmarkCShifted(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cs = shifted(
			cx.WithShift(i).
				WithOffset(15).
				WithShift(i + 15).
				WithOffset(30),
		)
	}
}
BenchmarkBShifted-32    1000000000    0.2108 ns/op    0 B/op    0 allocs/op
BenchmarkCShifted-32    1000000000    0.6351 ns/op    0 B/op    0 allocs/op

Оно просто превращается в число.

Исходная версия thegoldone, :

Возможно обратиться по указателю к переменной в Стеке метода родителя.

Вроде лежит на поверхности, а на самом деле нужно поразбираться.

Да, с этим стеком ещё куча приколов. Например цепочка:

package main

import "fmt"

type A struct {
	N     int
	Shift int
}

func (v A) WithShift(shift int) A {
	v.Shift = shift // всё равно копия, можно менять
	return v        // по месту и возвращать
}

// нет нужды копировать на стек A, используем указатель
func (v *A) Shifted() int {
	return v.N + v.Shift
}

func main() {
	var a = A{N: 10}

	fmt.Println("A shifted:", a.WithShift(20).Shifted(), "origin", a)
}

Получаем [play].

./prog.go:24:44: cannot call pointer method Shifted on A

Потому что на стеке. И указатель не даёт взять.

И мы попадаем в ситуацию или выделять, или выделять.

package main

import (
	"fmt"
	"testing"
)

type B struct {
	N     int
	Shift int
}

func (b B) WithShift(shift int) B {
	b.Shift = shift
	return b
}

func (b B) WithOffset(offset int) B {
	b.Shift += offset
	return b
}

func (b B) Shifted() int {
	return b.N + b.Shift
}

func (b B) String() string {
	return fmt.Sprintf("B{%d %+d}", b.N, b.Shift)
}

type C struct {
	N     int
	Shift int
}

func (c *C) WithShift(shift int) *C {
	return &C{N: c.N, Shift: shift}
}

func (c C) WithOffset(offset int) *C {
	return &C{N: c.N, Shift: c.Shift + offset}
}

func (c *C) Shifted() int {
	return c.N + c.Shift
}

func (c *C) String() string {
	return fmt.Sprintf("C{%d %+d}", c.N, c.Shift)
}

var (
	_bx  B
	_bxp *B
	_cx  C
	_cxp *C
	_b   string
	_c   string
)

func BenchmarkBAssign(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_bx = bx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkBAssignPtr(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		var t = bx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
		_bxp = &t
	}
}

func BenchmarkBSprintf(b *testing.B) {
	var bx = B{N: 10, Shift: 0}
	for i := range b.N {
		_b = fmt.Sprintf("%s",
			bx.WithShift(i).
				WithOffset(15).
				WithShift(i+15).
				WithOffset(30),
		)
	}
}

func BenchmarkCAssign(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cx = *cx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkCAssignPtr(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_cxp = cx.WithShift(i).
			WithOffset(15).
			WithShift(i + 15).
			WithOffset(30)
	}
}

func BenchmarkCSprintf(b *testing.B) {
	var cx = C{N: 10, Shift: 0}
	for i := range b.N {
		_c = fmt.Sprintf("%s",
			cx.WithShift(i).
				WithOffset(15).
				WithShift(i+15).
				WithOffset(30),
		)
	}
}

С результатом

# go test -benchmem -bench .
goos: linux
goarch: amd64
pkg: misc
cpu: AMD Ryzen 9 5950X 16-Core Processor            
BenchmarkBAssign-32       1000000000      0.4216 ns/op     0 B/op    0 allocs/op
BenchmarkBAssignPtr-32      88171006     13.26 ns/op      16 B/op    1 allocs/op
BenchmarkBSprintf-32         6228514    195.2 ns/op       56 B/op    4 allocs/op
BenchmarkCAssign-32       1000000000      0.4269 ns/op     0 B/op    0 allocs/op
BenchmarkCAssignPtr-32      79312046     13.60 ns/op      16 B/op    1 allocs/op
BenchmarkCSprintf-32         6245740    194.5 ns/op       56 B/op    4 allocs/op
PASS
ok      misc   6.045s

Во случае С мы сами выделяем новое значение. А в случае B оно выделяется во время fmt.Sprintf, и при сохранении указателя в глобальной переменной.

Причём Го оптимизирует цепочку выделений в одно (?) и итоговый результат одинаков для C и B.