Commit 8a6e51ae authored by Martin Möhrmann's avatar Martin Möhrmann

cmd/compile: generate makechan calls with int arguments

Where possible generate calls to runtime makechan with int arguments
during compile time instead of makechan with int64 arguments.

This eliminates converting arguments for calls to makechan with
int64 arguments for platforms where int64 values do not fit into
arguments of type int.

A similar optimization for makeslice was introduced in CL
golang.org/cl/27851.

386:
name                old time/op  new time/op  delta
MakeChan/Byte       52.4ns ± 6%  45.0ns ± 1%  -14.14%  (p=0.000 n=10+10)
MakeChan/Int        54.5ns ± 1%  49.1ns ± 1%   -9.87%  (p=0.000 n=10+10)
MakeChan/Ptr         150ns ± 1%   143ns ± 0%   -4.38%  (p=0.000 n=9+7)
MakeChan/Struct/0   49.2ns ± 2%  43.2ns ± 2%  -12.27%  (p=0.000 n=10+10)
MakeChan/Struct/32  81.7ns ± 2%  76.2ns ± 1%   -6.71%  (p=0.000 n=10+10)
MakeChan/Struct/40  88.4ns ± 2%  82.5ns ± 2%   -6.60%  (p=0.000 n=10+10)

AMD64:
name                old time/op  new time/op  delta
MakeChan/Byte       83.4ns ± 8%  80.8ns ± 3%    ~     (p=0.171 n=10+10)
MakeChan/Int         101ns ± 3%   101ns ± 2%    ~     (p=0.412 n=10+10)
MakeChan/Ptr         128ns ± 1%   128ns ± 1%    ~     (p=0.191 n=10+10)
MakeChan/Struct/0   67.6ns ± 3%  68.7ns ± 4%    ~     (p=0.224 n=10+10)
MakeChan/Struct/32   138ns ± 1%   139ns ± 1%    ~     (p=0.185 n=10+9)
MakeChan/Struct/40   154ns ± 1%   154ns ± 1%  -0.55%  (p=0.027 n=10+9)

Change-Id: Ie854cb066007232c5e9f71ea7d6fe27e81a9c050
Reviewed-on: https://go-review.googlesource.com/55140
Run-TryBot: Martin Möhrmann <moehrmann@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: 's avatarKeith Randall <khr@golang.org>
parent 4c557743
This diff is collapsed.
...@@ -116,7 +116,8 @@ func mapdelete_faststr(mapType *byte, hmap map[any]any, key any) ...@@ -116,7 +116,8 @@ func mapdelete_faststr(mapType *byte, hmap map[any]any, key any)
func mapiternext(hiter *any) func mapiternext(hiter *any)
// *byte is really *runtime.Type // *byte is really *runtime.Type
func makechan(chanType *byte, hint int64) (hchan chan any) func makechan64(chanType *byte, size int64) (hchan chan any)
func makechan(chanType *byte, size int) (hchan chan any)
func chanrecv1(hchan <-chan any, elem *any) func chanrecv1(hchan <-chan any, elem *any)
func chanrecv2(hchan <-chan any, elem *any) bool func chanrecv2(hchan <-chan any, elem *any) bool
func chansend1(hchan chan<- any, elem *any) func chansend1(hchan chan<- any, elem *any)
......
...@@ -1417,7 +1417,21 @@ opswitch: ...@@ -1417,7 +1417,21 @@ opswitch:
n = mkcall1(fn, nil, init, n.Left) n = mkcall1(fn, nil, init, n.Left)
case OMAKECHAN: case OMAKECHAN:
n = mkcall1(chanfn("makechan", 1, n.Type), n.Type, init, typename(n.Type), conv(n.Left, types.Types[TINT64])) // When size fits into int, use makechan instead of
// makechan64, which is faster and shorter on 32 bit platforms.
size := n.Left
fnname := "makechan64"
argtype := types.Types[TINT64]
// Type checking guarantees that TIDEAL size is positive and fits in an int.
// The case of size overflow when converting TUINT or TUINTPTR to TINT
// will be handled by the negative range checks in makechan during runtime.
if size.Type.IsKind(TIDEAL) || maxintval[size.Type.Etype].Cmp(maxintval[TUINT]) <= 0 {
fnname = "makechan"
argtype = types.Types[TINT]
}
n = mkcall1(chanfn(fnname, 1, n.Type), n.Type, init, typename(n.Type), conv(size, argtype))
case OMAKEMAP: case OMAKEMAP:
t := n.Type t := n.Type
......
...@@ -2072,7 +2072,7 @@ func MakeChan(typ Type, buffer int) Value { ...@@ -2072,7 +2072,7 @@ func MakeChan(typ Type, buffer int) Value {
if typ.ChanDir() != BothDir { if typ.ChanDir() != BothDir {
panic("reflect.MakeChan: unidirectional channel type") panic("reflect.MakeChan: unidirectional channel type")
} }
ch := makechan(typ.(*rtype), uint64(buffer)) ch := makechan(typ.(*rtype), buffer)
return Value{typ.common(), ch, flag(Chan)} return Value{typ.common(), ch, flag(Chan)}
} }
...@@ -2480,7 +2480,7 @@ func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, receive ...@@ -2480,7 +2480,7 @@ func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, receive
//go:noescape //go:noescape
func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool
func makechan(typ *rtype, size uint64) (ch unsafe.Pointer) func makechan(typ *rtype, size int) (ch unsafe.Pointer)
func makemap(t *rtype, cap int) (m unsafe.Pointer) func makemap(t *rtype, cap int) (m unsafe.Pointer)
//go:noescape //go:noescape
......
...@@ -55,11 +55,19 @@ type waitq struct { ...@@ -55,11 +55,19 @@ type waitq struct {
} }
//go:linkname reflect_makechan reflect.makechan //go:linkname reflect_makechan reflect.makechan
func reflect_makechan(t *chantype, size int64) *hchan { func reflect_makechan(t *chantype, size int) *hchan {
return makechan(t, size) return makechan(t, size)
} }
func makechan(t *chantype, size int64) *hchan { func makechan64(t *chantype, size int64) *hchan {
if int64(int(size)) != size {
panic(plainError("makechan: size out of range"))
}
return makechan(t, int(size))
}
func makechan(t *chantype, size int) *hchan {
elem := t.elem elem := t.elem
// compiler checks this but be safe. // compiler checks this but be safe.
...@@ -69,7 +77,7 @@ func makechan(t *chantype, size int64) *hchan { ...@@ -69,7 +77,7 @@ func makechan(t *chantype, size int64) *hchan {
if hchanSize%maxAlign != 0 || elem.align > maxAlign { if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment") throw("makechan: bad alignment")
} }
if size < 0 || int64(uintptr(size)) != size || (elem.size > 0 && uintptr(size) > (_MaxMem-hchanSize)/elem.size) { if size < 0 || (elem.size > 0 && uintptr(size) > (_MaxMem-hchanSize)/elem.size) {
panic(plainError("makechan: size out of range")) panic(plainError("makechan: size out of range"))
} }
......
...@@ -669,6 +669,59 @@ done: ...@@ -669,6 +669,59 @@ done:
<-ready2 <-ready2
} }
type (
struct0 struct{}
struct32 struct{ a, b, c, d int64 }
struct40 struct{ a, b, c, d, e int64 }
)
func BenchmarkMakeChan(b *testing.B) {
b.Run("Byte", func(b *testing.B) {
var x chan byte
for i := 0; i < b.N; i++ {
x = make(chan byte, 8)
}
close(x)
})
b.Run("Int", func(b *testing.B) {
var x chan int
for i := 0; i < b.N; i++ {
x = make(chan int, 8)
}
close(x)
})
b.Run("Ptr", func(b *testing.B) {
var x chan *byte
for i := 0; i < b.N; i++ {
x = make(chan *byte, 8)
}
close(x)
})
b.Run("Struct", func(b *testing.B) {
b.Run("0", func(b *testing.B) {
var x chan struct0
for i := 0; i < b.N; i++ {
x = make(chan struct0, 8)
}
close(x)
})
b.Run("32", func(b *testing.B) {
var x chan struct32
for i := 0; i < b.N; i++ {
x = make(chan struct32, 8)
}
close(x)
})
b.Run("40", func(b *testing.B) {
var x chan struct40
for i := 0; i < b.N; i++ {
x = make(chan struct40, 8)
}
close(x)
})
})
}
func BenchmarkChanNonblocking(b *testing.B) { func BenchmarkChanNonblocking(b *testing.B) {
myc := make(chan int) myc := make(chan int)
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
......
...@@ -8,8 +8,17 @@ ...@@ -8,8 +8,17 @@
package main package main
import (
"strings"
"unsafe"
)
type T chan int
const ptrSize = unsafe.Sizeof((*byte)(nil))
func main() { func main() {
c := make(chan int, 10) c := make(T, 10)
if len(c) != 0 || cap(c) != 10 { if len(c) != 0 || cap(c) != 10 {
println("chan len/cap ", len(c), cap(c), " want 0 10") println("chan len/cap ", len(c), cap(c), " want 0 10")
panic("fail") panic("fail")
...@@ -23,9 +32,39 @@ func main() { ...@@ -23,9 +32,39 @@ func main() {
panic("fail") panic("fail")
} }
c = make(chan int) c = make(T)
if len(c) != 0 || cap(c) != 0 { if len(c) != 0 || cap(c) != 0 {
println("chan len/cap ", len(c), cap(c), " want 0 0") println("chan len/cap ", len(c), cap(c), " want 0 0")
panic("fail") panic("fail")
} }
n := -1
shouldPanic("makechan: size out of range", func() { _ = make(T, n) })
shouldPanic("makechan: size out of range", func() { _ = make(T, int64(n)) })
if ptrSize == 8 {
n = 1 << 20
n <<= 20
shouldPanic("makechan: size out of range", func() { _ = make(T, n) })
n <<= 20
shouldPanic("makechan: size out of range", func() { _ = make(T, n) })
} else {
n = 1<<31 - 1
shouldPanic("makechan: size out of range", func() { _ = make(T, n) })
shouldPanic("makechan: size out of range", func() { _ = make(T, int64(n)) })
}
}
func shouldPanic(str string, f func()) {
defer func() {
err := recover()
if err == nil {
panic("did not panic")
}
s := err.(error).Error()
if !strings.Contains(s, str) {
panic("got panic " + s + ", want " + str)
}
}()
f()
} }
// errorcheck
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Ensure that typed non-integer, negative and to large
// values are not accepted as size argument in make for
// channels.
package main
type T chan byte
var sink T
func main() {
sink = make(T, -1) // ERROR "negative buffer argument in make.*"
sink = make(T, uint64(1<<63)) // ERROR "buffer argument too large in make.*"
sink = make(T, 0.5) // ERROR "constant 0.5 truncated to integer"
sink = make(T, 1.0)
sink = make(T, float32(1.0)) // ERROR "non-integer buffer argument in make.*"
sink = make(T, float64(1.0)) // ERROR "non-integer buffer argument in make.*"
sink = make(T, 1.0)
sink = make(T, float32(1.0)) // ERROR "non-integer buffer argument in make.*"
sink = make(T, float64(1.0)) // ERROR "non-integer buffer argument in make.*"
sink = make(T, 1+0i)
sink = make(T, complex64(1+0i)) // ERROR "non-integer buffer argument in make.*"
sink = make(T, complex128(1+0i)) // ERROR "non-integer buffer argument in make.*"
sink = make(T, 1+0i)
sink = make(T, complex64(1+0i)) // ERROR "non-integer buffer argument in make.*"
sink = make(T, complex128(1+0i)) // ERROR "non-integer buffer argument in make.*"
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment