Skip to content

Commit ff1cb2a

Browse files
Add pgo inline test case
1 parent c272284 commit ff1cb2a

File tree

5 files changed

+285
-1
lines changed

5 files changed

+285
-1
lines changed

api/go1.19.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,13 @@ pkg net/url, type URL struct, OmitHost bool #46059
244244
pkg os/exec, method (*Cmd) Environ() []string #50599
245245
pkg os/exec, type Cmd struct, Err error #43724
246246
pkg os/exec, var ErrDot error #43724
247+
pkg pgo/inline, func A() #43724
248+
pkg pgo/inline, func D(uint) int #43724
249+
pkg pgo/inline, func N(uint) *BS #43724
250+
pkg pgo/inline, func T(uint64) uint #43724
251+
pkg pgo/inline, method (*BS) NS(uint) (uint, bool) #43724
252+
pkg pgo/inline, method (*BS) S(uint) *BS #43724
253+
pkg pgo/inline, type BS struct #43724
247254
pkg regexp/syntax, const ErrNestingDepth = "expression nests too deeply" #51684
248255
pkg regexp/syntax, const ErrNestingDepth ErrorCode #51684
249256
pkg runtime/debug, func SetMemoryLimit(int64) int64 #48409

src/cmd/compile/internal/inline/inl.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlCalls *[]*ir.Inlin
951951
if _, ok := inlinedCallSites[pgo.CallSiteInfo{ir.Line(n), ir.CurFunc}]; !ok {
952952
inlinedCallSites[pgo.CallSiteInfo{ir.Line(n), ir.CurFunc}] = struct{}{}
953953
}
954-
if base.Flag.LowerM != 0 {
954+
if base.Flag.LowerM > 2 {
955955
fmt.Printf("Line %v: is definitely inlined\n", ir.Line(n))
956956
}
957957

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright 2017 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package test
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"internal/testenv"
11+
"io"
12+
"io/ioutil"
13+
"os/exec"
14+
"path/filepath"
15+
"regexp"
16+
"strings"
17+
"testing"
18+
)
19+
20+
// TestIntendedInlining tests that specific functions are inlined.
21+
// This allows refactoring for code clarity and re-use without fear that
22+
// changes to the compiler will cause silent performance regressions.
23+
func TestPGOIntendedInlining(t *testing.T) {
24+
testenv.MustHaveGoRun(t)
25+
t.Parallel()
26+
27+
// Make a temporary directory to work in.
28+
tmpdir, err := ioutil.TempDir("", "TestCode")
29+
if err != nil {
30+
t.Fatalf("Failed to create temporary directory: %v", err)
31+
}
32+
//defer os.RemoveAll(tmpdir)
33+
34+
want := map[string][]string{
35+
"pgo/inline": {
36+
"(*BS).NS",
37+
},
38+
}
39+
40+
// The functions which are not expected to be inlined are as follows.
41+
wantNot := map[string][]string{
42+
"pgo/inline": {
43+
// The calling edge main->A is hot and the cost of A is large than
44+
// inlineHotCalleeMaxBudget.
45+
"A",
46+
// The calling edge BenchmarkA" -> benchmarkB is cold
47+
// and the cost of A is large than inlineMaxBudget.
48+
"benchmarkB",
49+
},
50+
}
51+
52+
must := map[string]bool{
53+
"(*BS).NS": true,
54+
}
55+
56+
notInlinedReason := make(map[string]string)
57+
pkgs := make([]string, 0, len(want))
58+
for pname, fnames := range want {
59+
pkgs = append(pkgs, pname)
60+
for _, fname := range fnames {
61+
fullName := pname + "." + fname
62+
if _, ok := notInlinedReason[fullName]; ok {
63+
t.Errorf("duplicate func: %s", fullName)
64+
}
65+
notInlinedReason[fullName] = "unknown reason"
66+
}
67+
}
68+
69+
// If the compiler emit "cannot inline for function A", the entry A
70+
// in expectedNotInlinedList will be removed.
71+
expectedNotInlinedList := make(map[string]struct{})
72+
for pname, fnames := range wantNot {
73+
for _, fname := range fnames {
74+
fullName := pname + "." + fname
75+
expectedNotInlinedList[fullName] = struct{}{}
76+
}
77+
}
78+
79+
args := append([]string{"test", "-o", filepath.Join(tmpdir, "inline_hot.test"), "-bench=.", "-cpuprofile", filepath.Join(tmpdir, "inline_hot.pprof")}, pkgs...)
80+
gotool := testenv.GoToolPath(t)
81+
cmd := exec.Command(gotool, args...)
82+
var stdout, stderr bytes.Buffer
83+
cmd.Stdout = &stdout
84+
cmd.Stderr = &stderr
85+
err = cmd.Run()
86+
if err != nil {
87+
t.Fatalf("Failed: %v:\nOut: %s\nStderr: %s\n", err, &stdout, &stderr)
88+
}
89+
90+
args = append([]string{"test", "-run=nope", "-tags=", "-timeout=9m0s", "-gcflags=-m -m -profileuse " + filepath.Join(tmpdir, "inline_hot.pprof")}, pkgs...)
91+
cmd = testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
92+
93+
pr, pw := io.Pipe()
94+
cmd.Stdout = pw
95+
cmd.Stderr = pw
96+
cmdErr := make(chan error, 1)
97+
go func() {
98+
cmdErr <- cmd.Run()
99+
pw.Close()
100+
}()
101+
scanner := bufio.NewScanner(pr)
102+
curPkg := ""
103+
canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
104+
haveInlined := regexp.MustCompile(`: inlining call to ([^ ]*)`)
105+
cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
106+
for scanner.Scan() {
107+
line := scanner.Text()
108+
if strings.HasPrefix(line, "# ") {
109+
curPkg = line[2:]
110+
splits := strings.Split(curPkg, " ")
111+
curPkg = splits[0]
112+
continue
113+
}
114+
if m := haveInlined.FindStringSubmatch(line); m != nil {
115+
fname := m[1]
116+
delete(notInlinedReason, curPkg+"."+fname)
117+
continue
118+
}
119+
if m := canInline.FindStringSubmatch(line); m != nil {
120+
fname := m[1]
121+
fullname := curPkg + "." + fname
122+
// If function must be inlined somewhere, being inlinable is not enough
123+
if _, ok := must[fullname]; !ok {
124+
delete(notInlinedReason, fullname)
125+
continue
126+
}
127+
}
128+
if m := cannotInline.FindStringSubmatch(line); m != nil {
129+
fname, reason := m[1], m[2]
130+
fullName := curPkg + "." + fname
131+
if _, ok := notInlinedReason[fullName]; ok {
132+
// cmd/compile gave us a reason why
133+
notInlinedReason[fullName] = reason
134+
}
135+
delete(expectedNotInlinedList, fullName)
136+
continue
137+
}
138+
}
139+
if err := <-cmdErr; err != nil {
140+
t.Fatal(err)
141+
}
142+
if err := scanner.Err(); err != nil {
143+
t.Fatal(err)
144+
}
145+
for fullName, reason := range notInlinedReason {
146+
t.Errorf("%s was not inlined: %s", fullName, reason)
147+
}
148+
149+
// If the list expectedNotInlinedList is not empty, it indicates
150+
// the functions in the expectedNotInlinedList are marked with caninline.
151+
for fullName, _ := range expectedNotInlinedList {
152+
t.Errorf("%s was expected not inlined", fullName)
153+
}
154+
}

src/pgo/inline/inline_hot.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package main
2+
3+
import (
4+
"time"
5+
)
6+
7+
type BS struct {
8+
length uint
9+
s []uint64
10+
}
11+
12+
const wSize = uint(64)
13+
const lWSize = uint(6)
14+
15+
func D(i uint) int {
16+
return int((i + (wSize - 1)) >> lWSize)
17+
}
18+
19+
func N(length uint) (bs *BS) {
20+
bs = &BS{
21+
length,
22+
make([]uint64, D(length)),
23+
}
24+
25+
return bs
26+
}
27+
28+
func (b *BS) S(i uint) *BS {
29+
b.s[i>>lWSize] |= 1 << (i & (wSize - 1))
30+
return b
31+
}
32+
33+
var jn = [...]byte{
34+
0, 1, 56, 2, 57, 49, 28, 3, 61, 58, 42, 50, 38, 29, 17, 4,
35+
62, 47, 59, 36, 45, 43, 51, 22, 53, 39, 33, 30, 24, 18, 12, 5,
36+
63, 55, 48, 27, 60, 41, 37, 16, 46, 35, 44, 21, 52, 32, 23, 11,
37+
54, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6,
38+
}
39+
40+
func T(v uint64) uint {
41+
return uint(jn[((v&-v)*0x03f79d71b4ca8b09)>>58])
42+
}
43+
44+
func (b *BS) NS(i uint) (uint, bool) {
45+
x := int(i >> lWSize)
46+
if x >= len(b.s) {
47+
return 0, false
48+
}
49+
w := b.s[x]
50+
w = w >> (i & (wSize - 1))
51+
if w != 0 {
52+
return i + T(w), true
53+
}
54+
x = x + 1
55+
for x < len(b.s) {
56+
if b.s[x] != 0 {
57+
return uint(x)*wSize + T(b.s[x]), true
58+
}
59+
x = x + 1
60+
61+
}
62+
return 0, false
63+
}
64+
65+
func A() {
66+
s := N(100000)
67+
for i := 0; i < 1000; i += 30 {
68+
s.S(uint(i))
69+
}
70+
for j := 0; j < 1000; j++ {
71+
c := uint(0)
72+
for i, e := s.NS(0); e; i, e = s.NS(i + 1) {
73+
c++
74+
}
75+
}
76+
}
77+
78+
func main() {
79+
time.Sleep(time.Second)
80+
A()
81+
}

src/pgo/inline/inline_hot_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package main
2+
3+
import "testing"
4+
5+
func BenchmarkA(b *testing.B) {
6+
benchmarkB(b)
7+
}
8+
func benchmarkB(b *testing.B) {
9+
10+
for i := 0; true; {
11+
A()
12+
i = i + 1
13+
if i >= b.N {
14+
break
15+
}
16+
A()
17+
i = i + 1
18+
if i >= b.N {
19+
break
20+
}
21+
A()
22+
i = i + 1
23+
if i >= b.N {
24+
break
25+
}
26+
A()
27+
i = i + 1
28+
if i >= b.N {
29+
break
30+
}
31+
A()
32+
i = i + 1
33+
if i >= b.N {
34+
break
35+
}
36+
A()
37+
i = i + 1
38+
if i >= b.N {
39+
break
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)