将状态添加到通过pool.map调用的函数 - 如何避免酸洗错误

时间:2013-10-11 04:59:22

标签: python multithreading parallel-processing multiprocessing

我遇到了使用多处理模块时出现pickle错误的常见问题。

我的确切问题是我需要在我在pool.map函数中调用它之前给函数调用某个状态,但是这样做会导致attribute lookup __builtin__.function failed错误找到{{} 3}}。

基于链接的SO答案,看起来在pool.map中使用函数的唯一方法是调用已定义的函数本身,以便在当前函数的范围之外查找它。

我觉得我上面解释不好,所以这是代码中的问题。 :)

没有游泳池的测试

# Function to be called by the multiprocessing pool
def my_func(x):
    massive_list, medium_list, index1, index2 = x
    result = [massive_list[index1 + x][index2:] for x in xrange(10)]
    return result in medium_list



if __name__ == '__main__':

    data = [comprehension which loads a ton of state]
    source = [comprehension which also loads a medium amount of state]

    for num in range(100):
        to_crunch = ((massive_list, small_list, num, x) for x in range(1000)) 
        result = map(my_func, to_crunch)

这可以正常工作,就像预期一样。唯一“错误”的是它很慢。

游泳池尝试1

# (Note: my_func() remains the same)
if __name__ == '__main__':

    data = [comprehension which loads a ton of state]
    source = [comprehension which also loads a medium amount of state]

    pool = multiprocessing.Pool(2)
    for num in range(100):
        to_crunch = ((massive_list, small_list, num, x) for x in range(1000)) 
        result = pool.map(my_func, to_crunch)

这在技术上有效,但它的速度要慢18倍!减速必须来自于不仅在每次调用时复制两个海量数据结构,而且还在它们传递时对它们进行酸洗/去除。非池版本只需要将引用传递给大量列表,而不是实际列表。

因此,在追踪瓶颈后,我尝试将两个大量列表存储为my_func内的状态。这样,如果我理解正确,它只需要为每个工人复制一次(在我的情况下,4)。

Pool Attempt 2:

我将my_func包装在一个闭包中,将两个列表作为存储状态传递。

def build_myfunc(m,s):
    def my_func(x):
        massive_list = m # close the state in there
        small_list = s

        index1, index2 = x
        result = [massive_list[index1 + x][index2:] for x in xrange(10)]
        return result in medium_list
    return my_func

if __name__ == '__main__':

    data = [comprehension which loads a ton of state]
    source = [comprehension which also loads a medium amount of state]

    modified_func = build_myfunc(data, source)

    pool = multiprocessing.Pool(2)
    for num in range(100):
        to_crunch = ((massive_list, small_list, num, x) for x in range(1000)) 
        result = pool.map(modified_func, to_crunch)

但是,这会将pickle错误返回为(基于上面链接的SO问题),您无法从同一范围内调用具有多处理功能的函数。

错误:

PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

那么,有没有解决这个问题的方法呢?

1 个答案:

答案 0 :(得分:0)

地图是分配工作量的一种方式。如果你将数据存储在func中,我认为你已经消失了最初的目的。

让我们试着找为什么它慢一点。这不正常,必须有别的东西。

首先,进程数必须适合运行它们的机器。在您的示例中,您使用的是2个进程池,因此总共涉及3个进程。您正在使用的系统上有多少个核心?还在运行什么?在处理数据时系统负载是多少? 该功能对数据有何作用?它访问磁盘吗?或者它可能使用DB,这意味着可能有另一个进程访问磁盘和核心。 记忆呢?存储初始列表是否足够?

正确的实施是您的尝试1。

尝试使用iostat来分析执行情况。通过这种方式,您可以发现瓶颈。

如果它在cpu上停止,那么你可以尝试对代码进行一些调整。

来自Stackoverflow上的another answer(由我这样没问题复制并将其粘贴到此处:P):

您正在使用收集结果的.map()然后返回。因此,对于大型数据集,您可能会陷入收集阶段。

如果结果的顺序不重要,您可以尝试使用.imap() {{1>} 上的迭代器版本,甚至 .map() < / strong>(从你的例子看来)。

Here是相关文件。值得注意的是:

  

对于使用较大的chunksize值的非常长的迭代,可以使作业完成比使用默认值1更快。