Python,帮助并行化算法(尝试在线程池中包含线程池)

时间:2015-01-08 10:36:07

标签: python multithreading performance python-3.x concurrent.futures

我试图并行化一些计算,但我不明白为什么我的一个版本(我认为应该更快)比它慢。

简而言之,我有一个userIds列表(或多或少200)和placesId列表(或多或少2000万)。我需要计算每对用户/地点的分数。好事 是计算是完全相互独立的(取决于我们如何实现算法,甚至不需要返回结果)。

我已尝试过2种方法。

第一种方法

  1. 拉出主线程中的所有场所和所有用户
  2. 循环遍历所有用户并生成x线程(在我的情况下,我的小macbook 8似乎是最好的)

    with cf.ThreadPoolExecutor(max_workers=8) as executor:
        futures = [executor.submit(task,userId, placeIds) for userId in userIds]
    

    当所有期货完成后,我遍历所有期货并插入结果     在数据库中(worker任务返回一个列表[userId,placeId,score])

  3. 我有一个循环遍历所有地方并返回结果的任务

    def task(userId, placeIds):
        connection = pool.getconn()
        cursor = conn.cursor()
        #loop through all the places and call makeCalculation(cur, userId, placeId)
        pool.putconn(conn)
        return results
    
  4. 这位女士和温柔的男士让所有的用户/地点在10分钟内计算出来(而不是顺序的1.30小时))

    但是后来我虽然为什么不进行得分计算呢?因此,不是必须逐个遍历所有2000个位置的任务,而是在其他8个线程上生成计算。例如。

    第二种方法:

    基本上这种方法正在取代"任务"功能:

    with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
       futures = [ executor.submit(calculateScores,userId,placeId) for placeId in placeIds]
    

    我必须做的另一个修改是在calculateScores函数

    def calculateScores(userId,placeId):
       **connection = pool.getconn()
       cursor = connecton.cursor()**
       ...
        make a bunch of calculation by calling the database 1 or 2 times
    
       pool.putconn(conn)
       return [userId, placeId, score]
    

    因为你可以看到因为现在calculateScores本身将在8个//线程上,所以我不能共享数据库连接,否则我将得到竞争条件错误(然后脚本将崩溃1次中有3次)

    这种方法,我认为会更快,需要25分钟.....(而不是简单的for循环...)

    我90%确定这个速度较慢,因为现在每个任务都从池中获取数据库连接,这在某种程度上非常昂贵,因此速度很慢..

    有人可以给我一些建议,以便为我的场景充分利用并行化的最佳方法吗?

    将任务返回结果是一个好主意吗?或者我应该在calculateScores函数中准备好后立即将它们插入数据库?

    在ThreadPool中使用Threadpool是一种好习惯吗?

    我应该尝试一些多进程吗?

    谢谢你!

1 个答案:

答案 0 :(得分:1)

  

在ThreadPool中使用Threadpool是一种好习惯吗?

不,单个线程池就足够了,例如:

from concurrent.futures import ThreadPoolExecutor as Executor
from collections import deque

with Executor(max_workers=8) as executor:
    deque(executor.map(calculateScores, userIds, placeIds), maxlen=0)

如果数据库是应用程序的瓶颈(要找出,你可以模拟db调用),即,如果任务是I / O绑定,那么线程可以提高时间性能({{ 3}})因为GIL可以在Ithon(或其他阻塞OS)调用期间由python本身释放,或者在C扩展中释放,例如CPython的db驱动程序。

如果数据库处理好并发访问,那么每个线程都可以使用自己的数据库连接。注意:8线程可能比416线程更快 - 您需要对其进行测量。

时间性能可能在很大程度上取决于您如何构建数据库操作。见to a point

如果任务受CPU限制,例如,您为每个用户/地点ID执行一些昂贵的纯Python计算,那么您可以尝试ProcessPoolExecutor而不是ThreadPoolExecutor。确保在进程之间复制输入/输出数据不会主导计算本身。