如何使自定义对象可用于传递给dask df.apply的函数(无法序列化)

时间:2018-09-18 17:50:30

标签: dask dask-distributed

所有这些代码都可以在pandas中使用,但是运行单线程很慢。

我有一个创建缓慢的对象(这是一个bloom过滤器)。

我有类似下面的代码:

def has_match(row, my_filter):
    return my_filter.matches(
        a=row.a, b =row.b
    )

# ....make dask dataframe ddf

ddf['match'] = ddf.apply(has_match, args=(my_filter, ), axis=1, meta=(bool))
ddf.compute()

当我尝试运行此程序时,我收到一个开始的错误:

distributed.protocol.core - CRITICAL - Failed to Serialize

我的对象是从C库创建的,因此它不能自动进行序列化并不令我感到惊讶,但我不知道如何解决此问题。

2 个答案:

答案 0 :(得分:0)

仅使用线程

一种方法是完全避免该问题,并且根本不使用单独的进程。这样,您将不需要在它们之间序列化数据。

ddf.compute(scheduler='threads')

但这确实限制了您只能在一台计算机上的单个进程中运行,这可能不是您想要的。

弄清楚如何序列化对象

如果您能弄清楚如何将对象转换为字节串,则可以在对象上实现pickle协议(例如__getstate____setstate__方法,请参见Python文档)或您可以将定义添加到dask_serialize和dask_deserialize可调度函数。有关示例,请参见Dask's serialization docs

每次都重新创建对象

也许很难序列化对象,但是每个分区重新创建一次便宜吗?

def has_match(partition):
    my_filter = make_filter(...)
    return partition.apply(my_filter.matches(a=row.a, b =row.b))

ddf['match'] = ddf.map_partitions(has_match)

答案 1 :(得分:0)

Distributed希望所有中间结果都可序列化。就您而言,您有一个没有实现泡菜的对象。通常,您可以在此处选择几种方法(按最佳到最差的恕我直言):

  • 为此对象执行泡菜。请注意,使用copyreg模块可以为您不在控件中的类添加pickle支持。

  • 在函数中手动缓存过滤器的创建。您可以使用对象或模块中的全局变量来执行此操作。请注意,下面的这段代码需要成为导入模块的一部分,而不是您的交互式会话的一部分(即不在jupyter notebook / ipython会话中)。

例如(未试用):

myfilter = None


def get_or_load():
    global myfilter
    if myfilter is None:
        myfilter = load_filter()
    else:
        return myfilter


def load_filter():
    pass


def has_match(row):
    my_filter = get_or_load()
    return my_filter.matches(a=row.a, b=row.b)

然后在您的用户代码中:

from my_filter_utils import has_match

ddf['match'] = ddf.apply(has_match, axis=1, meta=('matches', bool))
  • 使用dask来管理缓存。为此,将对象包装在另一个类中,该类在序列化时会重新加载该对象。如果您随后将该对象保留在群集中,dask将保留该对象,并且最多在每个节点上调用一次创建函数。

例如(未试用):

from dask import delayed

class Wrapper(object):
    def __init__(self, func):
        self.func = func
        self.filter = func()

    def __reduce__(self):
        # When unpickled, the filter will be reloaded
        return (Wrapper, (func,))


def load_filter():
    pass


# Create a delayed function to load the filter
wrapper = delayed(Wrapper)(load_filter)

# Optionally persist the wrapper in the cluster, to be reused over multiple computations
wrapper = wrapper.persist()

def has_match(row, wrapper):
    return wrapper.filter.matches(a=row.a, b=row.b)


ddf['match'] = ddf.apply(has_match, args=(wrapper,), axis=1, meta=('matches', bool))