如何在Dask中实现增量缓存而不会出现数据重复?

时间:2017-06-28 08:43:34

标签: dask

我试图在Dask中找到相应的Spark unpersist。我的需要 对于明确的独立主义者,出现在以下情况:

  • 调用上下文已经存在大df,例如,因为它需要 计算许多聚合以进行预处理。
  • 调用上下文调用一个函数,该函数也需要调用persist,例如, 因为它执行一些迭代算法。

基本示例如下:

def iterative_algorithm(df, num_iterations):

    for iteration in range(num_iterations):

        # Transformation logic requiring e.g. map_partitions
        def mapper(df):
            # ...
            return df

        df = df.map_partitions(mapper)
        df = df.persist()
        # Now I would like to explicitly unpersist the old snapshot

    return df

在Spark中,可以通过释放旧快照来解决问题 明确。显然Dask没有明确的unpersist但是处理问题 通过参考计算标的期货。这意味着这个例子 上面会复制数据,因为调用上下文包含引用 旧的期货,而子功能持有对修改的引用 坚持。在我的实际用例中,有几个这样的嵌套级别 转换调用,导致数据甚至多次复制。

有没有办法在没有任何额外副本的情况下解决迭代缓存?

2 个答案:

答案 0 :(得分:2)

我会发布一些想法如何解决这个问题,但我仍然在寻找更好的方法 的替代品。

由于引用计数,避免副本是很棘手的,但那里 是可能性。问题是调用者持有引用的结果 原始df以及通过df = df.<method>创建新实例的子功能 调用。要解决这个问题,我们必须自己引用df 可变的。不幸的是,Python一般不允许改变引用 函数参数。

解决方案1:天真可变参考

解决该限制的最简单方法是将df包装到列表中 或者说。在这种情况下,子功能可以修改外部参考,例如,由:

df_list[0] = df_list[0].map_partitions(mapper)
df_list[0] = df_list[0].persist()

然而,这在语法上很尴尬,因此必须非常小心 通过df = df_list[0]简化语法再次创建新的引用 潜在的期货,可能导致数据重复。

解决方案2:基于包装器的可变引用

改进之后,可以编写一个包含引用的小包装类 到数据帧。通过这个包装器,子函数可以变异 参考资料。要改进语法问题,可以考虑使用包装器 应该自动将功能委托给数据框或继承 从中。总的来说,这个解决方案也感觉不对。

解决方案3:明确的突变

为了避免其他解决方案的语法问题,我目前更喜欢以下内容 变体,有效模拟map_partitions的可变版本 和persist通过原始df实例的原位修改。

def modify_inplace(old_df, new_df):
    # Currently requires accessing private fields of a DataFrame, but
    # maybe this could be officially supported by Dask.
    old_df.dask = new_df.dask
    old_df._meta = new_df._meta
    old_df._name = new_df._name
    old_df.divisions = new_df.divisions


def iterative_algorithm(df, num_iterations):

    for iteration in range(num_iterations):

        def mapper(df):
            # Actual transform logic...
            return df

        # Simulate mutable/in-place map_partitions
        new_df = df.map_partitions(mapper)
        modify_inplace(df, new_df)

        # Simulate mutable/in-place persist
        new_df = df.persist()
        modify_inplace(df, new_df)

    # Technically no need to return, because all operations were in-place
    return df

这对我来说相当不错,但需要仔细遵循这些规则:

  • 用上面的模式替换df = df.<method>之类的所有不可变调用。
  • 注意创建对df的引用。例如,在调用persist之前,为了语法上的方便使用some_col = df["some_sol"]这样的变量需要del some_col。否则,使用some_col存储的引用将再次导致数据重复。

答案 1 :(得分:2)

您可以按如下方式编写发布函数:

from distributed.client import futures_of

def release(collection):
    for future in futures_of(collection):
        future.release()

这只会释放当前实例。如果您有多个这些期货的实例,您可能需要多次调用它或添加如下所示的循环:

while future.client.refcount[future.key] > 0:

但是,如果你有其他副本有理由浮动,通常多次调用这一点似乎是不明智的。