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