"testing"
)
-// TestIntendedInlining tests that specific runtime functions are inlined.
+// TestIntendedInlining tests that specific functions are inlined.
// This allows refactoring for code clarity and re-use without fear that
// changes to the compiler will cause silent performance regressions.
func TestIntendedInlining(t *testing.T) {
"(*rngSource).Int63",
"(*rngSource).Uint64",
},
+ "net": {
+ "(*UDPConn).ReadFromUDP",
+ },
}
if runtime.GOARCH != "386" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
}
// ReadFromUDP acts like ReadFrom but returns a UDPAddr.
-func (c *UDPConn) ReadFromUDP(b []byte) (int, *UDPAddr, error) {
+func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err error) {
+ // This function is designed to allow the caller to control the lifetime
+ // of the returned *UDPAddr and thereby prevent an allocation.
+ // See https://blog.filippo.io/efficient-go-apis-with-the-inliner/.
+ // The real work is done by readFromUDP, below.
+ return c.readFromUDP(b, &UDPAddr{})
+}
+
+// readFromUDP implements ReadFromUDP.
+func (c *UDPConn) readFromUDP(b []byte, addr *UDPAddr) (int, *UDPAddr, error) {
if !c.ok() {
return 0, nil, syscall.EINVAL
}
- n, addr, err := c.readFrom(b)
+ n, addr, err := c.readFrom(b, addr)
if err != nil {
err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
// ReadFrom implements the PacketConn ReadFrom method.
func (c *UDPConn) ReadFrom(b []byte) (int, Addr, error) {
- if !c.ok() {
- return 0, nil, syscall.EINVAL
- }
- n, addr, err := c.readFrom(b)
- if err != nil {
- err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
- }
+ n, addr, err := c.readFromUDP(b, &UDPAddr{})
if addr == nil {
+ // Return Addr(nil), not Addr(*UDPConn(nil)).
return n, nil, err
}
return n, addr, err
"syscall"
)
-func (c *UDPConn) readFrom(b []byte) (n int, addr *UDPAddr, err error) {
+func (c *UDPConn) readFrom(b []byte, addr *UDPAddr) (int, *UDPAddr, error) {
buf := make([]byte, udpHeaderSize+len(b))
m, err := c.fd.Read(buf)
if err != nil {
buf = buf[:m]
h, buf := unmarshalUDPHeader(buf)
- n = copy(b, buf)
- return n, &UDPAddr{IP: h.raddr, Port: int(h.rport)}, nil
+ n := copy(b, buf)
+ *addr = UDPAddr{IP: h.raddr, Port: int(h.rport)}
+ return n, addr, nil
}
func (c *UDPConn) readMsg(b, oob []byte) (n, oobn, flags int, addr *UDPAddr, err error) {
return &UDPAddr{loopbackIP(net), a.Port, a.Zone}
}
-func (c *UDPConn) readFrom(b []byte) (int, *UDPAddr, error) {
- var addr *UDPAddr
+func (c *UDPConn) readFrom(b []byte, addr *UDPAddr) (int, *UDPAddr, error) {
n, sa, err := c.fd.readFrom(b)
switch sa := sa.(type) {
case *syscall.SockaddrInet4:
- addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port}
+ *addr = UDPAddr{IP: sa.Addr[0:], Port: sa.Port}
case *syscall.SockaddrInet6:
- addr = &UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneCache.name(int(sa.ZoneId))}
+ *addr = UDPAddr{IP: sa.Addr[0:], Port: sa.Port, Zone: zoneCache.name(int(sa.ZoneId))}
}
return n, addr, err
}
}
}
}
+
+func BenchmarkWriteToReadFromUDP(b *testing.B) {
+ conn, err := ListenUDP("udp4", new(UDPAddr))
+ if err != nil {
+ b.Fatal(err)
+ }
+ addr := conn.LocalAddr()
+ buf := make([]byte, 8)
+ b.ResetTimer()
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ _, err := conn.WriteTo(buf, addr)
+ if err != nil {
+ b.Fatal(err)
+ }
+ _, _, err = conn.ReadFromUDP(buf)
+ if err != nil {
+ b.Fatal(err)
+ }
+ }
+}