繁体   English   中英

peewee和peewee-async:为什么异步更慢

[英]peewee and peewee-async: why is async slower

我试图绕过Tornado和异步连接到Postgresql。 我找到了一个可以在http://peewee-async.readthedocs.io/en/latest/上执行此操作的库。

我设计了一个小测试来比较传统的Peewee和Peewee-async,但不知何故异步工作得更慢。

这是我的应用:

import peewee
import tornado.web
import logging
import asyncio
import peewee_async
import tornado.gen
import tornado.httpclient
from tornado.platform.asyncio import AsyncIOMainLoop

AsyncIOMainLoop().install()
app = tornado.web.Application(debug=True)
app.listen(port=8888)

# ===========
# Defining Async model
async_db = peewee_async.PooledPostgresqlDatabase(
    'reminderbot',
    user='reminderbot',
    password='reminderbot',
    host='localhost'
)
app.objects = peewee_async.Manager(async_db)
class AsyncHuman(peewee.Model):
    first_name = peewee.CharField()
    messenger_id = peewee.CharField()
    class Meta:
        database = async_db
        db_table = 'chats_human'


# ==========
# Defining Sync model
sync_db = peewee.PostgresqlDatabase(
    'reminderbot',
    user='reminderbot',
    password='reminderbot',
    host='localhost'
)
class SyncHuman(peewee.Model):
    first_name = peewee.CharField()
    messenger_id = peewee.CharField()
    class Meta:
        database = sync_db
        db_table = 'chats_human'

# defining two handlers - async and sync
class AsyncHandler(tornado.web.RequestHandler):

    async def get(self):
        """
        An asynchronous way to create an object and return its ID
        """
        obj = await self.application.objects.create(
            AsyncHuman, messenger_id='12345')
        self.write(
            {'id': obj.id,
             'messenger_id': obj.messenger_id}
        )


class SyncHandler(tornado.web.RequestHandler):

    def get(self):
        """
        An traditional synchronous way
        """
        obj = SyncHuman.create(messenger_id='12345')
        self.write({
            'id': obj.id,
            'messenger_id': obj.messenger_id
        })


app.add_handlers('', [
    (r"/receive_async", AsyncHandler),
    (r"/receive_sync", SyncHandler),
])

# Run loop
loop = asyncio.get_event_loop()
try:
    loop.run_forever()
except KeyboardInterrupt:
    print(" server stopped")

这是我从Apache Benchmark得到的:

ab -n 100 -c 100 http://127.0.0.1:8888/receive_async

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        2    4   1.5      5       7
Processing:   621 1049 256.6   1054    1486
Waiting:      621 1048 256.6   1053    1485
Total:        628 1053 255.3   1058    1492

Percentage of the requests served within a certain time (ms)
  50%   1058
  66%   1196
  75%   1274
  80%   1324
  90%   1409
  95%   1452
  98%   1485
  99%   1492
 100%   1492 (longest request)




ab -n 100 -c 100 http://127.0.0.1:8888/receive_sync
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        2    5   1.9      5       8
Processing:     8  476 277.7    479    1052
Waiting:        7  476 277.7    478    1052
Total:         15  481 276.2    483    1060

Percentage of the requests served within a certain time (ms)
  50%    483
  66%    629
  75%    714
  80%    759
  90%    853
  95%    899
  98%   1051
  99%   1060
 100%   1060 (longest request)

为什么同步更快? 我错过了哪个瓶颈?

长篇解释:

http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/

简单解释一下:同步Python代码很简单,大部分是在标准库的socket模块中实现的,它是纯粹的C.异步Python代码比同步代码更复杂。 每个请求都需要多次执行主事件循环代码,这些代码是用Python编写的(在这里的asyncio情况下),因此与C代码相比有很多开销。

像你这样的基准显着显示异步的开销,因为你的应用程序和数据库之间没有网络延迟,而且你正在进行大量非常小的数据库操作。 由于基准测试的每个其他方面都很快,因此事件循环逻辑的这些执行会增加总运行时的很大一部分。

上面链接的Mike Bayer的论点是,像这样的低延迟场景对于数据库应用程序来说是典型的,因此不应该在事件循环上运行数据库操作。

Async最适用于高延迟场景,例如websockets和web crawler,其中应用程序花费大部分时间等待对等,而不是花费大部分时间来执行Python。

总而言之:如果你的应用程序有充分的理由保持异步(它处理慢速对等),拥有异步数据库驱动程序是一个好主意,为了一致的代码,但期望一些开销。

如果由于其他原因不需要异步,请不要执行异步数据库调用,因为它们有点慢。

数据库ORM为异步体系结构引入了许多复杂性。 ORM中有几个位置可能会发生阻塞,并且可能会压倒性地改为异步形式。 阻塞发生的位置也可能因数据库而异。 我猜你的结果如此之慢的原因是因为事件循环中存在大量未经优化的调用(我可能会出现严重错误,这些天我主要使用SQLAlchemy或原始SQL)。 根据我的经验,在一个线程中执行数据库代码通常会更快,并在结果可用时产生结果。 我不能真正代表PeeWee,但是SQLAlchemy非常适合在多个线程中运行,并且没有太多的缺点(但确实存在的非常非常烦人)。

我建议你尝试使用ThreadPoolExecutor和同步Peewee模块进行实验,并在一个线程中运行数据库函数。 您将不得不对主代码进行更改,但如果您问我,那将是值得的。 例如,假设您选择使用回调代码,那么您的ORM查询可能如下所示:

from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(max_workers=10)

def queryByName(name):
    query = executor.submit(db_model.findOne, name=name)
    query.add_done_callback(processResult)

def processResult(query):
    orm_obj = query.results()
    # do stuff with the results

您可以在协同程序中使用yeild fromawait ,但这对我来说有点问题。 另外,我还不熟悉协同程序。 只要开发人员小心死锁,数据库会话和事务,这个代码段就可以与Tornado一起使用。 如果线程出现问题,这些因素确实会减慢您的应用程序速度。

如果您感觉非常冒险,MagicStack(asyncio背后的公司)有一个名为asyncpg的项目,它应该非常快! 我一直想尝试,但没有找到时间:(

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM