net: optimize WriteMsgUDPAddrPort

This is one step towards optimizing WriteMsgUDPAddrPort.
Further steps remain, namely to avoid the syscall.Sockaddr interface,
as we do for UDPConn.WriteToUDP and UDPConn.ReadFromUDP.

A previous change optimized ReadMsgUDPAddrPort by having
ReadMsgUDP call ReadMsgUDPAddrPort rather than the other way around.

This change does not implement WriteMsgUDP in terms of WriteMsgUDPAddrPort,
because a few layers deep, on posix platforms only
(in ipToSockaddrInet4 and ipToSockaddrInet6),
is special handling of zero-length IP addresses and IPv4zero.
It treats IP(nil) as equivalent to 0.0.0.0 or ::,
and 0.0.0.0 as equivalent to :: in an IPv6 context.

Based on the comments, I suspect that this treatment was intended
for the Listen* API, not the Write* API, but it affects both,
and I am nervous about changing the behavior for Write*.

The netip package doesn't have a way to represent a "zero-length IP address"
as distinct from an invalid IP address (which is a good thing),
so to implement WriteMsgUDP using WriteMsgUDPAddrPort,
we would have to duplicate this special handling at the start of WriteMsgUDP.
But this handling depends on whether the UDPConn is an IPv4 or an IPv6 conn,
which is also platform-specific information.

As a result, every attempt I made to implement WriteMsgUDP using
WriteMsgUDPAddrPort while preserving behavior ended up
being considerably worse than copy/paste/modify.

This does mean that WriteMsgUDP and WriteMsgUDPAddrPort will have
different behavior in these cases.

name                       old time/op    new time/op    delta
ReadWriteMsgUDPAddrPort-8    5.29µs ± 6%    5.02µs ± 7%   -5.14%  (p=0.000 n=13+15)

name                       old alloc/op   new alloc/op   delta
ReadWriteMsgUDPAddrPort-8      128B ± 0%       64B ± 0%  -50.00%  (p=0.000 n=15+15)

name                       old allocs/op  new allocs/op  delta
ReadWriteMsgUDPAddrPort-8      4.00 ± 0%      2.00 ± 0%  -50.00%  (p=0.000 n=15+15)

Change-Id: Ia78eb49734f4301d7772dfdbb5a87e4d303a9f7a
Reviewed-on: https://go-review.googlesource.com/c/go/+/360597
Trust: Josh Bleecher Snyder <josharian@gmail.com>
Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
This commit is contained in:
Josh Bleecher Snyder 2021-11-01 13:34:08 -07:00
parent 3c61cb3dcd
commit 6f1e9a9c21
4 changed files with 77 additions and 5 deletions

View file

@ -9,6 +9,7 @@ package net
import (
"context"
"internal/poll"
"net/netip"
"runtime"
"syscall"
)
@ -196,3 +197,32 @@ func ipToSockaddr(family int, ip IP, port int, zone string) (syscall.Sockaddr, e
}
return nil, &AddrError{Err: "invalid address family", Addr: ip.String()}
}
func addrPortToSockaddrInet4(ap netip.AddrPort) (syscall.SockaddrInet4, error) {
// ipToSockaddrInet4 has special handling here for zero length slices.
// We do not, because netip has no concept of a generic zero IP address.
addr := ap.Addr()
if !addr.Is4() {
return syscall.SockaddrInet4{}, &AddrError{Err: "non-IPv4 address", Addr: addr.String()}
}
sa := syscall.SockaddrInet4{
Addr: addr.As4(),
Port: int(ap.Port()),
}
return sa, nil
}
func addrPortToSockaddrInet6(ap netip.AddrPort) (syscall.SockaddrInet6, error) {
// ipToSockaddrInet6 has special handling here for zero length slices.
// We do not, because netip has no concept of a generic zero IP address.
addr := ap.Addr()
if !addr.Is6() {
return syscall.SockaddrInet6{}, &AddrError{Err: "non-IPv6 address", Addr: addr.String()}
}
sa := syscall.SockaddrInet6{
Addr: addr.As16(),
Port: int(ap.Port()),
ZoneId: uint32(zoneCache.index(addr.Zone())),
}
return sa, nil
}

View file

@ -114,6 +114,13 @@ func UDPAddrFromAddrPort(addr netip.AddrPort) *UDPAddr {
}
}
// An addrPortUDPAddr is a netip.AddrPort-based UDP address that satisfies the Addr interface.
type addrPortUDPAddr struct {
netip.AddrPort
}
func (addrPortUDPAddr) Network() string { return "udp" }
// UDPConn is the implementation of the Conn and PacketConn interfaces
// for UDP network connections.
type UDPConn struct {
@ -244,11 +251,14 @@ func (c *UDPConn) WriteMsgUDP(b, oob []byte, addr *UDPAddr) (n, oobn int, err er
// WriteMsgUDPAddrPort is like WriteMsgUDP but takes a netip.AddrPort instead of a UDPAddr.
func (c *UDPConn) WriteMsgUDPAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error) {
// TODO(bradfitz): make this efficient, making the internal net package
// type throughout be netip.Addr and only converting to the net.IP slice
// version at the edge. But for now (2021-10-20), this is a wrapper around
// the old way.
return c.WriteMsgUDP(b, oob, UDPAddrFromAddrPort(addr))
if !c.ok() {
return 0, 0, syscall.EINVAL
}
n, oobn, err = c.writeMsgAddrPort(b, oob, addr)
if err != nil {
err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addrPortUDPAddr{addr}, Err: err}
}
return
}
func newUDPConn(fd *netFD) *UDPConn { return &UDPConn{conn{fd}} }

View file

@ -57,6 +57,10 @@ func (c *UDPConn) writeMsg(b, oob []byte, addr *UDPAddr) (n, oobn int, err error
return 0, 0, syscall.EPLAN9
}
func (c *UDPConn) writeMsgAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error) {
return 0, 0, syscall.EPLAN9
}
func (sd *sysDialer) dialUDP(ctx context.Context, laddr, raddr *UDPAddr) (*UDPConn, error) {
fd, err := dialPlan9(ctx, sd.network, laddr, raddr)
if err != nil {

View file

@ -123,6 +123,34 @@ func (c *UDPConn) writeMsg(b, oob []byte, addr *UDPAddr) (n, oobn int, err error
return c.fd.writeMsg(b, oob, sa)
}
func (c *UDPConn) writeMsgAddrPort(b, oob []byte, addr netip.AddrPort) (n, oobn int, err error) {
if c.fd.isConnected && addr.IsValid() {
return 0, 0, ErrWriteToConnected
}
if !c.fd.isConnected && !addr.IsValid() {
return 0, 0, errMissingAddress
}
switch c.fd.family {
case syscall.AF_INET:
sa, err := addrPortToSockaddrInet4(addr)
if err != nil {
return 0, 0, err
}
// TODO: Implement writeMsgInet4 to avoid allocation converting sa to an interface.
return c.fd.writeMsg(b, oob, &sa)
case syscall.AF_INET6:
sa, err := addrPortToSockaddrInet6(addr)
if err != nil {
return 0, 0, err
}
// TODO: Implement writeMsgInet6 to avoid allocation converting sa to an interface.
return c.fd.writeMsg(b, oob, &sa)
default:
return 0, 0, &AddrError{Err: "invalid address family", Addr: addr.Addr().String()}
}
}
func (sd *sysDialer) dialUDP(ctx context.Context, laddr, raddr *UDPAddr) (*UDPConn, error) {
fd, err := internetSocket(ctx, sd.network, laddr, raddr, syscall.SOCK_DGRAM, 0, "dial", sd.Dialer.Control)
if err != nil {