device: reduce redundant per-packet overhead in RX path

Peer.RoutineSequentialReceiver() deals with packet vectors and does not
need to perform timer and endpoint operations for every packet in a
given vector. Changing these per-packet operations to per-vector
improves throughput by as much as 10% in some environments.

Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
Jordan Whited 2023-11-07 15:24:21 -08:00 committed by Jordan Whited
parent 8cc8b8b11b
commit cc193a0b32
2 changed files with 17 additions and 8 deletions

View file

@ -445,7 +445,9 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
return return
} }
elemsContainer.Lock() elemsContainer.Lock()
for _, elem := range elemsContainer.elems { validTailPacket := -1
dataPacketReceived := false
for i, elem := range elemsContainer.elems {
if elem.packet == nil { if elem.packet == nil {
// decryption failed // decryption failed
continue continue
@ -455,21 +457,19 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
continue continue
} }
peer.SetEndpointFromPacket(elem.endpoint) validTailPacket = i
if peer.ReceivedWithKeypair(elem.keypair) { if peer.ReceivedWithKeypair(elem.keypair) {
peer.SetEndpointFromPacket(elem.endpoint)
peer.timersHandshakeComplete() peer.timersHandshakeComplete()
peer.SendStagedPackets() peer.SendStagedPackets()
} }
peer.keepKeyFreshReceiving()
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketReceived()
peer.rxBytes.Add(uint64(len(elem.packet) + MinMessageSize)) peer.rxBytes.Add(uint64(len(elem.packet) + MinMessageSize))
if len(elem.packet) == 0 { if len(elem.packet) == 0 {
device.log.Verbosef("%v - Receiving keepalive packet", peer) device.log.Verbosef("%v - Receiving keepalive packet", peer)
continue continue
} }
peer.timersDataReceived() dataPacketReceived = true
switch elem.packet[0] >> 4 { switch elem.packet[0] >> 4 {
case 4: case 4:
@ -512,6 +512,15 @@ func (peer *Peer) RoutineSequentialReceiver(maxBatchSize int) {
bufs = append(bufs, elem.buffer[:MessageTransportOffsetContent+len(elem.packet)]) bufs = append(bufs, elem.buffer[:MessageTransportOffsetContent+len(elem.packet)])
} }
if validTailPacket >= 0 {
peer.SetEndpointFromPacket(elemsContainer.elems[validTailPacket].endpoint)
peer.keepKeyFreshReceiving()
peer.timersAnyAuthenticatedPacketTraversal()
peer.timersAnyAuthenticatedPacketReceived()
}
if dataPacketReceived {
peer.timersDataReceived()
}
if len(bufs) > 0 { if len(bufs) > 0 {
_, err := device.tun.device.Write(bufs, MessageTransportOffsetContent) _, err := device.tun.device.Write(bufs, MessageTransportOffsetContent)
if err != nil && !device.isClosed() { if err != nil && !device.isClosed() {

View file

@ -436,7 +436,7 @@ func calculatePaddingSize(packetSize, mtu int) int {
return paddedSize - lastUnit return paddedSize - lastUnit
} }
/* Encrypts the elems in the queue /* Encrypts the elements in the queue
* and marks them for sequential consumption (by releasing the mutex) * and marks them for sequential consumption (by releasing the mutex)
* *
* Obs. One instance per core * Obs. One instance per core
@ -495,7 +495,7 @@ func (peer *Peer) RoutineSequentialSender(maxBatchSize int) {
return return
} }
if !peer.isRunning.Load() { if !peer.isRunning.Load() {
// peer has been stopped; return re-usable elemsContainer to the shared pool. // peer has been stopped; return re-usable elems to the shared pool.
// This is an optimization only. It is possible for the peer to be stopped // This is an optimization only. It is possible for the peer to be stopped
// immediately after this check, in which case, elem will get processed. // immediately after this check, in which case, elem will get processed.
// The timers and SendBuffers code are resilient to a few stragglers. // The timers and SendBuffers code are resilient to a few stragglers.