Skip to content

UDPTransport.sendto spends a lot of time validating the address #214

Closed
@jlaine

Description

@jlaine
  • uvloop version: 0.11.3 and 0.12.0rc2
  • Python version: 3.7
  • Platform: Linux

I have been trying to optimize the performance of aiortc, an asyncio-based WebRTC implementation. aiortc uses aioice (an Interactive Connectivity Establishment library) for its network access. Using uvloop provides some performance improvement, but not as much as I had hoped.

During my optimization work I noticed that when called with an address, UDPTransport.sendto spends a noticeable amount of time calling into Python code, looking up the socket's family and type properties. Looking at the code, this corresponds to validation checks on the destination address which are run on every single packet (which involves a call to getaddrinfo whose result is ultimately discarded):

if addr is not None and self.sock.family != socket.AF_UNIX:

I am aware that specifying a remote address when calling create_datagram_endpoint would probably work around this issue, but in the case of ICE this is not practical as the remote party may have multiple addresses, which are not known in advance and may change over time.

To assess the impact of the validation code I tried a basic patch which stores the last validated address and skips the validation if sendto is called for the same address again. I then ran the following test:

import asyncio
import socket
import time

import uvloop


class DummyProtocol(asyncio.DatagramProtocol):
    pass


async def run(loop):
    addr = ('127.0.0.1', 1234)
    data = b'M' * 1500
    transport, protocol = await loop.create_datagram_endpoint(
        lambda: DummyProtocol(),
        family=socket.AF_INET)

    start = time.process_time()
    for i in range(1000000):
        transport.sendto(data, addr)
    print('elapsed', time.process_time() - start)


asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
loop = asyncio.get_event_loop()
loop.run_until_complete(run(loop))

On my laptop the results of sending out a million packets:

  • without the patch: 7.8s
  • with the patch: 3.0s

Questions:

  • do we need the validation code at all?
  • if we do, would you consider a patch which caches the last successfully validated address and skips the validation if sendto is called again with the same address?
  • alternatively, could we at the very least store the socket type and family to avoid the call into Python to fetch these two properties?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions