fix unwind tests

This commit is contained in:
Felix Geisendörfer 2021-04-12 10:30:54 +02:00
parent 81352c678a
commit ed2fa54799
4 changed files with 3179 additions and 620 deletions

View file

@ -7,35 +7,17 @@ This directory contains benchmarks to explore which factors impact stack unwindi
The benchmark below shows that stack unwinding has O(N) complexity with regard to the number of call frames on the stack: The benchmark below shows that stack unwinding has O(N) complexity with regard to the number of call frames on the stack:
``` ```
BenchmarkStackDepth/8-12 2208600 547.2 ns/op BenchmarkStackDepth/8-12 1968214 612.2 ns/op
BenchmarkStackDepth/16-12 1447922 810.8 ns/op BenchmarkStackDepth/16-12 975457 1184 ns/op
BenchmarkStackDepth/32-12 915291 1338 ns/op BenchmarkStackDepth/32-12 572706 2101 ns/op
BenchmarkStackDepth/64-12 488719 2366 ns/op BenchmarkStackDepth/64-12 333598 3596 ns/op
BenchmarkStackDepth/128-12 264735 4462 ns/op BenchmarkStackDepth/128-12 182450 6561 ns/op
BenchmarkStackDepth/256-12 137575 8643 ns/op BenchmarkStackDepth/256-12 94783 12548 ns/op
BenchmarkStackDepth/512-12 68355 17316 ns/op BenchmarkStackDepth/512-12 48439 24471 ns/op
BenchmarkStackDepth/1024-12 34710 34810 ns/op BenchmarkStackDepth/1024-12 24884 48310 ns/op
``` ```
## Result 2: O(N) Function Size Tests were done on my local machine:
Perhaps suprisingly, stack unwinding is also O(N) with regard to the size of the generated machine code for the function:
```
BenchmarkFunctionSize/1-12 2562176 462.8 ns/op
BenchmarkFunctionSize/2-12 2509465 484.7 ns/op
BenchmarkFunctionSize/4-12 2356609 504.6 ns/op
BenchmarkFunctionSize/8-12 2095870 568.3 ns/op
BenchmarkFunctionSize/16-12 1778889 669.7 ns/op
BenchmarkFunctionSize/32-12 1396009 856.0 ns/op
BenchmarkFunctionSize/64-12 943807 1269 ns/op
BenchmarkFunctionSize/128-12 516487 2271 ns/op
BenchmarkFunctionSize/256-12 277821 4490 ns/op
```
## Disclaimer
YMMV, and especially the function size also depends on the program counter at which the function is being unwound. All tests were done on my local machine:
``` ```
go test -bench . go test -bench .

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,8 @@ import (
"os" "os"
) )
//go:generate go run .
func main() { func main() {
if err := run(); err != nil { if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
@ -14,5 +16,61 @@ func main() {
} }
func run() error { func run() error {
return nil file, err := os.Create("generated.go")
if err != nil {
return err
}
defer file.Close()
fmt.Fprint(file, `// Code generated by go generate; DO NOT EDIT.
package main
//go:noinline
func stackdepth1(fn func()) { fn() }
`)
max := 1024
for i := 2; i <= max; i++ {
fmt.Fprintf(
file,
"//go:noinline\nfunc stackdepth%d(fn func()) { stackdepth%d(fn) }\n",
i,
i-1,
)
}
fmt.Fprint(file, "\nvar stackdepth = map[int]func(func()) {\n")
for i := 1; i <= max; i++ {
fmt.Fprintf(file, "\t%d: stackdepth%d,\n", i, i)
}
fmt.Fprint(file, "}\n")
// Disabled: Turned out to be a deadend
//for i := 1; i <= max; i *= 2 {
//fmt.Fprintf(file, "//go:noinline\n")
//fmt.Fprintf(file, "func funcsize%d(v0, n int, fn func()) int {\n", i)
//for n := 1; n <= i; n++ {
//fmt.Fprintf(file, "\tv%d := v%d\n", n, n-1)
//fmt.Fprintf(file, "\tv%d = (^v%d + %d)\n", n, n, n)
//fmt.Fprintf(file, "\tif v%d == 23 {\n", n)
//sum := []string{}
//for j := 0; j <= n; j++ {
//sum = append(sum, fmt.Sprintf("v%d", j))
//}
//fmt.Fprintf(file, "\t\treturn %s\n", strings.Join(sum, " + "))
//fmt.Fprintf(file, "\t}\n")
//}
//fmt.Fprintf(file, "\tfn()\n")
//fmt.Fprintf(file, "\treturn -1\n")
//fmt.Fprintf(file, "}\n\n")
//}
//fmt.Fprint(file, "\nvar funcsize = map[int]func(int, int, func()) int {\n")
//for i := 1; i <= max; i *= 2 {
//fmt.Fprintf(file, "\t%d: funcsize%d,\n", i, i)
//}
//fmt.Fprint(file, "}\n")
return file.Close()
} }

View file

@ -11,10 +11,10 @@ import (
func BenchmarkStackDepth(b *testing.B) { func BenchmarkStackDepth(b *testing.B) {
for d := 8; d <= 1024; d = d * 2 { for d := 8; d <= 1024; d = d * 2 {
b.Run(fmt.Sprintf("%d", d), func(b *testing.B) { b.Run(fmt.Sprintf("%d", d), func(b *testing.B) {
callers := make([]uintptr, d) callers := make([]uintptr, d*2)
atStackDepth(d, func() { atStackDepth(d, func() {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
if n := runtime.Callers(0, callers); n != d { if n := runtime.Callers(1, callers); n != d {
b.Fatalf("got=%d want=%d", n, d) b.Fatalf("got=%d want=%d", n, d)
} }
} }
@ -23,598 +23,38 @@ func BenchmarkStackDepth(b *testing.B) {
} }
} }
// atStackDepth recursively calls itself until the number of stack frames // atStackDepth calls functions that call each other until there are depth-1
// reaches the desired depth, and then calls fn. // functions on the stack and then calls fn.
//go:noinline
func atStackDepth(depth int, fn func()) { func atStackDepth(depth int, fn func()) {
pcs := make([]uintptr, depth+10) pcs := make([]uintptr, depth+10)
n := runtime.Callers(1, pcs) remaining := depth - runtime.Callers(1, pcs) - 1
if n > depth { if remaining < 1 {
panic("depth exceeded") panic("can't simulate desired stack depth: too low")
} else if n < depth { } else if remaining == 1 {
atStackDepth(depth, fn) fn()
return } else if f, ok := stackdepth[remaining]; !ok {
} panic("can't simulate desired stack depth: no map entry")
} else {
fn() f(fn)
}
func BenchmarkFunctionSize(b *testing.B) {
m := map[int]func(func()){
1: dostuff1,
2: dostuff2,
4: dostuff4,
8: dostuff8,
16: dostuff16,
32: dostuff32,
64: dostuff64,
128: dostuff128,
256: dostuff256,
}
var callers = make([]uintptr, 32)
for s := 1; s <= 256; s = s * 2 {
b.Run(fmt.Sprintf("%d", s), func(b *testing.B) {
m[s](func() {
for i := 0; i < b.N; i++ {
runtime.Callers(0, callers)
}
})
})
} }
} }
// dostuff is supposed to generate a bunch of machine code that will hopefully // disabled: turned out to be a deadend
// be inlined into its callers. //func BenchmarkFunctionSize(b *testing.B) {
func dostuff() int { //var callers = make([]uintptr, 32)
m := map[int]string{} //for s := 1; s <= 1024; s = s * 2 {
m[0] = "foo" //b.Run(fmt.Sprintf("%d", s), func(b *testing.B) {
m[100] = "bar" //called := false
return len(m) //funcsize[s](0, 0, func() {
} //for i := 0; i < b.N; i++ {
//runtime.Callers(0, callers)
func dostuff1(fn func()) { //}
dostuff() //called = true
fn() //})
} //if !called {
//b.Fatal("not called")
func dostuff2(fn func()) { //}
dostuff() //})
dostuff() //}
fn() //}
}
func dostuff4(fn func()) {
dostuff()
dostuff()
dostuff()
dostuff()
fn()
}
func dostuff8(fn func()) {
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
fn()
}
func dostuff16(fn func()) {
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
fn()
}
func dostuff32(fn func()) {
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
fn()
}
func dostuff64(fn func()) {
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
fn()
}
func dostuff128(fn func()) {
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
fn()
}
func dostuff256(fn func()) {
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
dostuff()
fn()
}