История изменений
Исправление 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.