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

如何使自定义对象可用于传递给dask df.apply的函数(无法序列化),dask,dask-distributed,Dask,Dask Distributed,所有这些代码都在pandas中工作,但运行单线程的速度很慢 我有一个对象(它是一个bloom过滤器),创建速度很慢 我有dask代码,看起来像: 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,

所有这些代码都在pandas中工作,但运行单线程的速度很慢

我有一个对象(它是一个bloom过滤器),创建速度很慢

我有dask代码,看起来像:

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库创建的,所以它不能自动序列化并不奇怪,但我不知道如何解决这个问题。

只使用线程 一种方法是完全避免这个问题,完全不使用单独的流程。这样,您就不需要在它们之间序列化数据

ddf.compute(scheduler='threads')
但这确实限制了您在一台机器上的单个进程中运行,这可能不是您想要的

了解如何序列化对象 如果您能够找出如何将对象转换为bytestring,然后返回,那么您可以在对象上实现pickle协议(如
\uuu getstate\uuuuuuu
\uuuu setstate\uuuuu
方法,请参见Python文档),或者可以向dask\u序列化和dask\u反序列化可调度函数添加定义。有关示例,请参见

每次都重新创建对象 也许序列化对象很难,但在每个分区重新创建一次却很便宜

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)

Distributed希望所有中间结果都可以序列化。在您的例子中,有一个对象没有实现pickle。一般来说,您在这里有几个选项(按最佳到最差IMHO的顺序):

  • 为此对象实现pickle。请注意,使用该模块可以为不在您控制范围内的类添加pickle支持

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

例如(未经测试):

然后在用户代码中:

from my_filter_utils import has_match

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


我明确地试图摆脱单线程。这个物体大约需要20秒来制作。我认为这些选项都没有帮助。第二个选项可能会有帮助。听起来你需要弄清楚如何序列化你的对象,以便它可以在机器之间移动。关于序列化,你可能是对的,但我已经使用pybind11从c库生成了我的对象。c库有一个序列化方法,但我不知道如何使用pybind11来包装它,这不是我将要死的剑,因为我花了好几天才到达今天的位置。请原谅我的态度。我真的很感谢你抽出时间…漫长的一天。谢谢。我尝试了两种方法。使用dask方法似乎更快,由于设置对象的成本,我在样本数据集上的速度没有我希望的那么快,但一旦我运行了所有数据,这将是一个巨大的好处。你的代码中有一个小的输入错误。在
\uuuu reduce\uuu
方法中,它应该是
self.func
。虽然我认为手动缓存代码更容易查看。速度的提高,以及我所有的代码都可以保存在我的笔记本中这一事实,意味着延迟包装器解决方案将很好地为我工作。
from my_filter_utils import has_match

ddf['match'] = ddf.apply(has_match, axis=1, meta=('matches', bool))
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))