1

我正在编写一个脚本来并行进行数百万个 API 调用。

为此,我正在使用带有 aiohttp 的 Python 3.6。我原以为 uvloop 会让它更快,但它似乎让它变慢了。难道我做错了什么?

使用 uvloop:22 秒

没有 uvloop:15 秒

import asyncio
import aiohttp
import uvloop
import time
import logging

from aiohttp import ClientSession, TCPConnector

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger()

urls = ["http://www.yahoo.com","http://www.bbcnews.com","http://www.cnn.com","http://www.buzzfeed.com","http://www.walmart.com","http://www.emirates.com","http://www.kayak.com","http://www.expedia.com","http://www.apple.com","http://www.youtube.com"]
bigurls = 10 * urls

def run(enable_uvloop):
    try:
        if enable_uvloop:
            loop = uvloop.new_event_loop()
        else:
            loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        start = time.time()
        conn = TCPConnector(limit=5000, use_dns_cache=True, loop=loop, verify_ssl=False)
        with ClientSession(connector=conn) as session:
            tasks = asyncio.gather(*[asyncio.ensure_future(do_request(url, session)) for url in bigurls]) # tasks to do
            results = loop.run_until_complete(tasks) # loop until done
            end = time.time()
            logger.debug('total time:')
            logger.debug(end - start)
            return results
        loop.close()
    except Exception as e:
        logger.error(e, exc_info=True)

async def do_request(url, session):
    """
    """
    try:
        async with session.get(url) as response:
            resp = await response.text()
            return resp
    except Exception as e:
        logger.error(e, exc_info=True)

run(True)
#run(False)
4

3 回答 3

1

你不是一个人; 实际上我刚刚得到了类似的结果(这导致我用谷歌搜索我的发现并把我带到了这里)。

我的实验涉及使用 aiohttp 向 Google.com 运行 500 个并发 GET 请求。

这是供参考的代码:

import asyncio, aiohttp, concurrent.futures
from datetime import datetime
import uvloop


class UVloopTester():
    def __init__(self):
        self.timeout = 20
        self.threads = 500
        self.totalTime = 0
        self.totalRequests = 0

    @staticmethod
    def timestamp():
        return f'[{datetime.now().strftime("%H:%M:%S")}]'

    async def getCheck(self):
        async with aiohttp.ClientSession() as session:
            response = await session.get('https://www.google.com', timeout=self.timeout)
            response.close()
        await session.close()
        return True

    async def testRun(self, id):
        now = datetime.now()
        try:
            if await self.getCheck():
                elapsed = (datetime.now() - now).total_seconds()
                print(f'{self.timestamp()} Request {id} TTC: {elapsed}')
                self.totalTime += elapsed
                self.totalRequests += 1
        except concurrent.futures._base.TimeoutError: print(f'{self.timestamp()} Request {id} timed out')

    async def main(self):
        await asyncio.gather(*[asyncio.ensure_future(self.testRun(x)) for x in range(self.threads)])

    def start(self):
        # comment these lines to toggle
        uvloop.install()
        asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

        loop = asyncio.get_event_loop()
        now = datetime.now()
        loop.run_until_complete(self.main())
        elapsed = (datetime.now() - now).total_seconds()
        print(f'{self.timestamp()} Main TTC: {elapsed}')
        print()
        print(f'{self.timestamp()} Average TTC per Request: {self.totalTime / self.totalRequests}')
        if len(asyncio.Task.all_tasks()) > 0:
            for task in asyncio.Task.all_tasks(): task.cancel()
            try: loop.run_until_complete(asyncio.gather(*asyncio.Task.all_tasks()))
            except asyncio.CancelledError: pass
        loop.close()


test = UVloopTester()
test.start()

我没有计划并执行任何仔细的实验​​来记录我的发现并计算标准偏差和 p 值。但是我已经运行了这个(累人的)次数,并得出了以下结果。

不使用 uvloop 运行:

  • loop.run_until_complete(main()) 大约需要 10 秒。
  • 完成请求的平均时间约为 4 秒。

使用 uvloop 运行:

  • loop.run_until_complete(main()) 大约需要 16 秒。
  • 完成请求的平均时间约为 8.5 秒。

我已经与我的一位朋友分享了这段代码,他实际上是建议我尝试 uvloop 的人(因为他从中获得了速度提升)。运行几次后,他的结果证实他确实看到使用 uvloop 确实提高了速度(平均而言,完成 main() 和请求的时间更短)

我们的发现让我相信,我们发现的差异与我们的设置有关:我在中端笔记本电脑上使用具有 8 GB RAM 的 Debian 虚拟机,而他使用的是具有更多“肌肉”的本机 Linux 桌面'在引擎盖下。

我对你的问题的回答是:不,我不相信你做错了什么,因为我正在经历同样的结果,而且看起来我没有做错任何事,尽管欢迎和赞赏任何建设性的批评。

我希望我能提供更多帮助;我希望我的插话能有点用处。

于 2019-10-09T05:11:13.760 回答
0

我尝试了一个类似的实验,发现并行 http GET 的 uvloop 和 asyncio 事件循环之间没有真正的区别:

asyncio event loop: avg=3.6285968542099 s. stdev=0.5583842811362075 s.
uvloop event loop: avg=3.419699764251709 s. stdev=0.13423859428541632 s.

可能是 uvloop 的显着优势在它用于服务器代码时发挥作用,即用于处理许多传入请求。

代码:

import time
from statistics import mean, stdev
import asyncio
import uvloop
import aiohttp

urls = [
    'https://aws.amazon.com', 'https://google.com', 'https://microsoft.com', 'https://www.oracle.com/index.html'
    'https://www.python.org', 'https://nodejs.org', 'https://angular.io', 'https://www.djangoproject.com',
    'https://reactjs.org', 'https://www.mongodb.com', 'https://reinvent.awsevents.com',
    'https://kafka.apache.org', 'https://github.com', 'https://slack.com', 'https://authy.com',
    'https://cnn.com', 'https://fox.com', 'https://nbc.com', 'https://www.aljazeera.com',
    'https://fly4.emirates.com', 'https://www.klm.com', 'https://www.china-airlines.com',
    'https://en.wikipedia.org/wiki/List_of_Unicode_characters', 'https://en.wikipedia.org/wiki/Windows-1252'
]

def timed(func):
    async def wrapper():
        start = time.time()
        await func()
        return time.time() - start
    return wrapper

@timed
async def main():
    conn = aiohttp.TCPConnector(use_dns_cache=False)
    async with aiohttp.ClientSession(connector=conn) as session:
        coroutines = [fetch(session, url) for url in urls]
        await asyncio.gather(*coroutines)

async def fetch(session, url):
    async with session.get(url) as resp:
        await resp.text()

asycio_results = [asyncio.run(main()) for i in range(10)]
print(f'asyncio event loop: avg={mean(asycio_results)} s. stdev={stdev(asycio_results)} s.')

# Change to uvloop
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())

uvloop_results = [asyncio.run(main()) for i in range(10)]
print(f'uvloop event loop: avg={mean(uvloop_results)} s. stdev={stdev(uvloop_results)} s.')
于 2018-11-29T10:56:47.727 回答
0

aiohttp建议使用aiodns

另外,我记得,这with ClientSession(connector=conn) as session:应该是异步的

于 2019-07-14T12:36:34.430 回答