3

I'm getting fairly different results with two different implementations.

Here is implementation 1

request_semaphore = asyncio.Semaphore(5)
async def _send_async_request(client: AsyncClient, method, auth, url, body):
  async with request_semaphore:
    try:
      async for attempt in AsyncRetrying(stop=stop_after_attempt(3), wait=wait_fixed(1)):
        with attempt:    
          response = await client.request(method=method, url=url, auth=auth, json=body)
          response.raise_for_status()
          return response
    except RetryError as e:
      pass

And here is implementation 2:

request_semaphore = asyncio.Semaphore(5)
@retry(stop=stop_after_attempt(3), wait=wait_fixed(1))
async def _send_single_async_request(self, client: AsyncClient, method, auth, url, body):
  async with request_semaphore:
    response = await client.request(method=method, url=url, auth=auth, json=body)
    response.raise_for_status()
    return response

async def _send_async_request(self, client: AsyncClient, method, auth, url, body):
  try:
    await self._send_single_async_request(client, method, auth, request)
  except RetryError as e:
    pass

I'm testing it against a stable REST API. Here are the bench marks:

  1. 100 successful POST requests:
  • Implementation 1: 0:59 mins
  • Implementation 2: 0:57 mins
  1. 100 failed POST requests:
  • Implementation 1: 3:26 mins
  • Implementation 2: 2:09 mins

These results are consistent. Can anyone help me understand why my first implementation is slower than my second?

edit: FYI, here's how i'm calling the above functions (the above funcs actually receive a request tuple with url and body, edited it for clarity)

async def _prepare_async_requests(method, auth, requests):
    async with AsyncClient() as client:
      task_list = [self._send_async_request(client, method, auth, request) for request in requests]
      return [await task for task in asyncio.as_completed(task_list)]

def send_async_requests(auth, method, requests):
    loop = asyncio.get_event_loop()
    responses = loop.run_until_complete(self._prepare_async_requests(method, auth, requests))
    return responses
JTa
  • 181
  • 1
  • 12
  • I guess it's due to the different code structure. In the first case you use a semaphore for url X until it exceeds the number of trials or succeeds. Instead, for the second example, all semaphores are shared, so as soon as one finishes, it leaves the space for another one. So, it's like waiting for a long task to finish instead of parallelizing it. That's my guess – lsabi Oct 06 '20 at 09:47

0 Answers0