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代码简单,主要在标准库的套接字模块中实现,是纯C语言。异步Python代码比同步代码复杂。每个请求都需要多次执行主事件循环代码,它是用 Python 编写的(这里是 asyncio
的情况),因此与 C 代码相比有很多开销。
像您这样的基准测试显着地显示了异步的开销,因为您的应用程序和数据库之间没有网络延迟,而且您正在执行大量非常小的数据库操作。由于基准测试的每个 other 方面都很快,因此事件循环逻辑的这些多次执行增加了总 运行 时间的很大一部分。
上面链接的 Mike Bayer 的论点是像这样的低延迟场景对于数据库应用程序来说是典型的,因此数据库操作不应该 运行 在事件循环中。
异步最适合高延迟场景,例如 websockets 和网络爬虫,应用程序将大部分时间花在等待对等点上,而不是花大部分时间执行 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 from
或 await
,但这对我来说有点问题。另外,我还不太精通协程。只要开发人员注意死锁、数据库会话和事务,此代码段应该可以很好地与 Tornado 配合使用。如果线程中出现问题,这些因素确实会减慢您的应用程序。
如果您喜欢冒险,MagicStack(asyncio 背后的公司)有一个名为 asyncpg
的项目,它应该非常快!一直想试试,一直没时间:(
我正在努力思考 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代码简单,主要在标准库的套接字模块中实现,是纯C语言。异步Python代码比同步代码复杂。每个请求都需要多次执行主事件循环代码,它是用 Python 编写的(这里是 asyncio
的情况),因此与 C 代码相比有很多开销。
像您这样的基准测试显着地显示了异步的开销,因为您的应用程序和数据库之间没有网络延迟,而且您正在执行大量非常小的数据库操作。由于基准测试的每个 other 方面都很快,因此事件循环逻辑的这些多次执行增加了总 运行 时间的很大一部分。
上面链接的 Mike Bayer 的论点是像这样的低延迟场景对于数据库应用程序来说是典型的,因此数据库操作不应该 运行 在事件循环中。
异步最适合高延迟场景,例如 websockets 和网络爬虫,应用程序将大部分时间花在等待对等点上,而不是花大部分时间执行 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 from
或 await
,但这对我来说有点问题。另外,我还不太精通协程。只要开发人员注意死锁、数据库会话和事务,此代码段应该可以很好地与 Tornado 配合使用。如果线程中出现问题,这些因素确实会减慢您的应用程序。
如果您喜欢冒险,MagicStack(asyncio 背后的公司)有一个名为 asyncpg
的项目,它应该非常快!一直想试试,一直没时间:(