如何使用线程来提高pymongo性能?

5

我正在尝试提高pymongo的性能,但是我没有观察到任何改进。

我的样本数据库有400,000条记录。基本上,我看到线程和单线程性能相等 - 唯一的性能增益来自多个进程执行。

pymongo在查询期间是否不释放GIL?

单线程性能:real 0m0.618s

多进程:real 0m0.144s

多线程:real 0m0.656s

常规代码:

choices = ['foo','bar','baz']


def regular_read(db, sample_choice):
    rows = db.test_samples.find({'choice':sample_choice})
    return 42  # done to remove calculations from the picture

def main():
    client = MongoClient('localhost', 27017)
    db = client['test-async']
    for sample_choice in choices:
        regular_read(db, sample_choice)

if __name__ == '__main__':
    main()

$ time python3 mongotest_read.py 

real    0m0.618s
user    0m0.085s
sys 0m0.018s

现在当我使用多进程技术时,我可以看到一些改善。

from random import randint, choice

import functools
from pymongo import MongoClient
from concurrent import futures

choices = ['foo','bar','baz']
MAX_WORKERS = 4

def regular_read(sample_choice):
    client = MongoClient('localhost', 27017,connect=False)
    db = client['test-async']
    rows = db.test_samples.find({'choice':sample_choice})
    #return sum(r['data'] for r in rows)
    return 42

def main():
    f = functools.partial(regular_read)
    with futures.ProcessPoolExecutor(MAX_WORKERS) as executor:
        res = executor.map(f, choices)

    print(list(res))
    return len(list(res))

if __name__ == '__main__':
    main()

$ time python3 mongotest_proc_read.py 
[42, 42, 42]

real    0m0.144s
user    0m0.106s
sys 0m0.041s

但是当您从ProcessPoolExecutor切换到ThreadPoolExecutor时,速度会降回单线程模式。

...

def main():
    client = MongoClient('localhost', 27017,connect=False)
    f = functools.partial(regular_read, client)
    with futures.ThreadPoolExecutor(MAX_WORKERS) as executor:
        res = executor.map(f, choices)

    print(list(res))
    return len(list(res))

$ time python3 mongotest_thread_read.py 
[42, 42, 42]

real    0m0.656s 
user    0m0.111s
sys 0m0.024s

...


1
我也尝试过为每个线程提供自己的MongoClient - 结果是一样的。 - MPaz
2个回答

20

PyMongo使用标准的Python socket模块,在发送和接收网络数据时会放弃GIL。然而,瓶颈不在于MongoDB或网络,而是在于Python。

CPU密集型的Python进程不能通过增加线程来扩展;事实上,由于上下文切换和其他低效率问题,它们会略微变慢。要使用多于一个CPU的Python进程,请启动子进程。

我知道,“查找”应该是CPU密集型不符合直觉,但Python解释器足够慢以与我们的直觉相矛盾。如果查询很快,本地主机上没有与MongoDB有关的延迟,MongoDB可以轻松胜过Python客户端。您刚刚运行的实验,用子进程替换线程,证实了Python性能是瓶颈。

为确保最大吞吐量,请确保启用了C扩展:pymongo.has_c() == True。这样一来,PyMongo可以像Python客户端库可以实现的那样快速运行,如需获得更高的吞吐量,请转向多进程。

如果您预期的真实场景涉及耗时更长的查询或具有某些网络延迟的远程MongoDB,则多线程可能会提高性能。


2
线程中大部分的 CPU 工作是将 MongoDB 的 BSON 转换为字典对象。使用 document_class=RawBSONDocument 可以在使用线程时克服这个瓶颈(在某种程度上)。 - odedfos


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接