我正在尝试制作一个多处理MongoDB实用程序,它完全正常工作,但我认为我遇到了性能问题......即使有20名工作人员,它每秒处理的文档也不超过2800个...我想我可以快5倍......这是我的代码,它没有做任何特殊的事情,只是将剩余的时间打印到光标的末尾。
也许有更好的方法在MongoDB游标上执行多处理,因为我需要在每个拥有17.4M记录集的doc上运行一些东西,因此性能和时间都是必须的。
START = time.time()
def remaining_time(a, b):
if START:
y = (time.time() - START)
z = ((a * y) / b) - y
d = time.strftime('%H:%M:%S', time.gmtime(z))
e = round(b / y)
progress("{0}/{1} | Tiempo restante {2} ({3}p/s)".format(b, a, d, e), b, a)
def progress(p, c, t):
pc = (c * 100) / t
sys.stdout.write("%s [%-20s] %d%%\r" % (p, '█' * (pc / 5), pc))
sys.stdout.flush()
def dowork(queue):
for p, i, pcount in iter(queue.get, 'STOP'):
remaining_time(pcount, i)
def populate_jobs(queue):
mongo_query = {}
products = MONGO.mydb.items.find(mongo_query, no_cursor_timeout=True)
if products:
pcount = products.count()
i = 1
print "Procesando %s productos..." % pcount
for p in products:
try:
queue.put((p, i, pcount))
i += 1
except Exception, e:
utils.log(e)
continue
queue.put('STOP')
def main():
queue = multiprocessing.Queue()
procs = [multiprocessing.Process(target=dowork, args=(queue,)) for _ in range(CONFIG_POOL_SIZE)]
for p in procs:
p.start()
populate_jobs(queue)
for p in procs:
p.join()
另外,我注意到大约每2500个aprox文件,脚本暂停约0.5-1秒,这显然是一个不好的问题。这是MongoDB问题,因为如果我执行完全相同的循环但使用range(0, 1000000)
脚本根本不会暂停并以每秒57,000次迭代运行,总共20秒来结束脚本。 。与每秒2,800个MongoDB文档的巨大差异...
这是运行1,000,000次迭代循环而不是docs的代码。
def populate_jobs(queue):
mongo_query = {}
products = MONGO.mydb.items.find(mongo_query, no_cursor_timeout=True)
if products:
pcount = 1000000
i = 1
print "Procesando %s productos..." % pcount
for p in range(0, 1000000):
queue.put((p, i, pcount))
i += 1
queue.put('STOP')
更新
正如我所看到的,问题不在于多处理本身,光标是否填充了未在多处理模式下运行的Queue
,这是一个填充Queue
(populateJobs
方法的简单过程)也许如果我可以使光标多线程/多镜像并且并行填充Queue
它将更快填充,那么多处理方法dowork
会更快,因为我认为有一个瓶颈我只在Queue
中每秒填写大约2,800项,并在dowork
多进程中检索更多内容,但我不知道如何并行化MongoDB
光标。
也许,问题是我的计算机和服务器的MongoDB之间的延迟。在我要求下一个游标和MongoDB告诉我哪个游标之间的延迟使我的性能降低了2000%(从61,000 str / s到2,800 doc / s) NOPE 我已尝试过本地主机MongoDB并且性能完全一样......这让我感到疯狂
答案 0 :(得分:3)
以下是如何使用Pool
为孩子们提供的服务:
START = time.time()
def remaining_time(a, b):
if START:
y = (time.time() - START)
z = ((a * y) / b) - y
d = time.strftime('%H:%M:%S', time.gmtime(z))
e = round(b / y)
progress("{0}/{1} | Tiempo restante {2} ({3}p/s)".format(b, a, d, e), b, a)
def progress(p, c, t):
pc = (c * 100) / t
sys.stdout.write("%s [%-20s] %d%%\r" % (p, '█' * (pc / 5), pc))
sys.stdout.flush()
def dowork(args):
p, i, pcount = args
remaining_time(pcount, i)
def main():
queue = multiprocessing.Queue()
procs = [multiprocessing.Process(target=dowork, args=(queue,)) for _ in range(CONFIG_POOL_SIZE)]
pool = multiprocessing.Pool(CONFIG_POOL_SIZE)
mongo_query = {}
products = MONGO.mydb.items.find(mongo_query, no_cursor_timeout=True)
pcount = products.count()
pool.map(dowork, ((p, idx, pcount) for idx,p in enumerate(products)))
pool.close()
pool.join()
请注意,使用pool.map
需要立即将光标中的所有内容加载到内存中,这可能是一个问题,因为它有多大。您可以使用imap
来避免一次性使用整个内容,但是您需要指定chunksize
来最小化IPC开销:
# Calculate chunksize using same algorithm used internally by pool.map
chunksize, extra = divmod(pcount, CONFIG_POOL_SIZE * 4)
if extra:
chunksize += 1
pool.imap(dowork, ((p, idx, pcount) for idx,p in enumerate(products)), chunksize=chunksize)
pool.close()
pool.join()
对于1,000,000件物品,它提供了12,500个大块。您可以尝试比这更大和更小的尺寸,并查看它如何影响性能。
我不确定这会有多大帮助,如果瓶颈实际上只是将数据从MongoDB中拉出来。
答案 1 :(得分:1)
为什么使用多处理?您似乎没有使用队列在其他线程中进行实际工作。 Python有一个global interpreter lock,这使得多线程代码的性能低于您的预期。它可能使这个程序变慢,而不是更快。
一些表现提示:
尝试将find()调用中的batch_size
设置为某个大号(例如20000)。这是在客户端获取更多内容之前一次返回的最大文档数,默认值为101.
尝试将cursor_type
设置为pymongo.cursor.CursorType.EXHAUST
,这可能会减少您所看到的延迟。