]> Cypherpunks repositories - gostls13.git/commitdiff
net: optimize WriteMsgUDPAddrPort
authorJosh Bleecher Snyder <josharian@gmail.com>
Mon, 1 Nov 2021 20:34:08 +0000 (13:34 -0700)
committerJosh Bleecher Snyder <josharian@gmail.com>
Tue, 2 Nov 2021 03:55:19 +0000 (03:55 +0000)
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>
src/net/ipsock_posix.go
src/net/udpsock.go
src/net/udpsock_plan9.go
src/net/udpsock_posix.go

index 0910f63d488b7410442b9132712b053eb6c20481..e433e8a91c75c0b025e657a98bebc33da9658e6e 100644 (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
+}
index 8c97ca753775f0c81191a52ad0df93bc29ca2c9b..0d563fd4f5c1e8595a164e73f18a21553756786b 100644 (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}} }
index c18af2356d1fe3fe162152c3baab3698b9e3c1bc..ac5afa22810006aef6bfbc14c92a17a290b25dcf 100644 (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 {
index b20025140f2dd0691d3b63fc368c89fa843e13d5..646687d148fa8257b906ea093f3ce967328da780 100644 (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 {