是否有用于从 julia 中的类似生成器的函数创建快速迭代器的宏?

Posted

技术标签:

【中文标题】是否有用于从 julia 中的类似生成器的函数创建快速迭代器的宏?【英文标题】:Is there a macro for creating fast Iterators from generator-like functions in julia? 【发布时间】:2017-09-20 09:41:03 【问题描述】:

从 python3 到 Julia,希望能够将 fast 迭代器编写为具有生产/产量语法或类似内容的函数。

Julia 的宏似乎表明可以构建一个宏,将这种“生成器”函数转换为 julia 迭代器。 [看起来您甚至可以轻松地内联以函数样式编写的迭代器,这是 Iterators.jl 包还尝试为其特定迭代器提供的功能https://github.com/JuliaCollections/Iterators.jl#the-itr-macro-for-automatic-inlining-in-for-loops]

只是举个例子说明我的想法:

@asiterator function myiterator(as::Array)
  b = 1
  for (a1, a2) in zip(as, as[2:end])
    try
      @produce a1[1] + a2[2] + b
    catch exc
    end
  end
end

for i in myiterator([(1,2), (3,1), 3, 4, (1,1)])
   @show i
end

myiterator 在理想情况下应该创建一个开销尽可能低的快速迭代器。当然,这只是一个具体的例子。理想情况下,我希望拥有适用于所有或几乎所有生成器功能的东西。

目前推荐的将生成器函数转换为迭代器的方法是通过 Julia 的任务,至少据我所知。然而,它们似乎也比纯迭代器慢得多。例如,如果您可以使用 imapchain 等简单的迭代器(由 Iterators.jl 包提供)来表达您的函数,这似乎是非常可取的。

理论上是否有可能在 julia 中构建一个将生成器风格的函数转换为灵活的 fast 迭代器的宏?

Extra-Point-Question:如果可能的话,是否有一个内联此类迭代器的通用宏?

【问题讨论】:

Channels,在 0.6 中替换 Tasks,应该比 Tasks 快得多。 我还没有听说过它们,并且我阅读了很多关于 julia 的文档。谢谢指点! Channels 不会取代 Tasks --- 它们是 Tasks 可以用来交流的一种机制。 【参考方案1】:

Python 风格的生成器——在 Julia 中最接近于从任务中产生——涉及相当多的固有开销。您必须切换任务,这是非常重要的,并且不能直接被编译器消除。这就是为什么 Julia 的迭代器基于转换一个通常不可变的简单状态值和另一个的函数。长话短说:不,我不相信这种转变可以自动完成。

【讨论】:

【参考方案2】:

这种形式的一些迭代器可以这样写:

myiterator(as) = (a1[1] + a2[2] + 1 for (a1, a2) in zip(as, as[2:end]))

此代码可以(可能)内联。

为了完全概括这一点,理论上可以编写一个将其参数转换为连续传递样式 (CPS) 的宏,从而可以暂停和重新启动执行,提供类似于迭代器的东西。带分隔符的延续特别适用于此 (https://en.wikipedia.org/wiki/Delimited_continuation)。结果是一大堆匿名函数,这可能比任务切换更快,但不一定,因为在一天结束时它需要堆分配相似数量的状态。

我碰巧在这里有这样一个转换的例子(虽然是在 femtolisp 中,而不是 Julia):https://github.com/JeffBezanson/femtolisp/blob/master/examples/cps.lsp 这以 define-generator 宏结束,该宏执行您所描述的操作。但我不确定为 Julia 做这件事是否值得。

【讨论】:

【参考方案3】:

在考虑了很多如何在不损失太多性能的情况下将 python 生成器转换为 Julia 之后,我实现并测试了一个更高级别的函数库,该库以延续样式实现类 Python/类任务生成器。 https://github.com/schlichtanders/Continuables.jl

本质上,我们的想法是将 Python 的 yield / Julia 的 produce 视为我们从外部作为额外参数获取的函数。我称它为cont 以继续。看看这个范围的重新实现的例子

crange(n::Integer) = cont -> begin
  for i in 1:n
    cont(i)
  end
end

您可以通过以下代码简单地将所有整数相加

function sum_continuable(continuable)
  a = Ref(0)
  continuable() do i
    a.x += i
  end
  a.x
end

# which simplifies with the macro Continuables.@Ref to
@Ref function sum_continuable(continuable)
  a = Ref(0)
  continuable() do i
    a += i
  end
  a
end

sum_continuable(crange(4))  # 10

正如您所希望的那样,您可以像在 python 中使用生成器或在 julia 中使用任务一样使用可连续对象。使用do 表示法而不是for 循环是您必须习惯的一件事。

这个想法会让你走得很远。使用这个想法不能完全实现的唯一标准方法是zip。所有其他标准的高级工具都可以像您希望的那样工作。

在某些情况下,性能比任务快得令人难以置信,甚至比迭代器还要快(特别是 Continuables.cmap 的幼稚实现比 Iterators.imap 快几个数量级)。查看 github 存储库 https://github.com/schlichtanders/Continuables.jl 的 Readme.md 了解更多详细信息。


编辑:为了更直接地回答我自己的问题,不需要宏@asiterator,直接使用延续样式即可。

mycontinuable(as::Array) = cont -> begin
  b = 1
  for (a1, a2) in zip(as, as[2:end])
    try
      cont(a1[1] + a2[2] + b)
    catch exc
    end
  end
end

mycontinuable([(1,2), (3,1), 3, 4, (1,1)]) do i
   @show i
end

【讨论】:

以上是关于是否有用于从 julia 中的类似生成器的函数创建快速迭代器的宏?的主要内容,如果未能解决你的问题,请参考以下文章

为 Julia 集生成自定义调色板

Julia 中是不是存在类似 Python 的 virtualenv?

我可以在 julia 中定义一个类似于原始数据类型的新类型吗?

如何清除或删除 Julia 中的全局变量?

从 Julia 中的文本文件中读取数据矩阵

尝试读取存储在 Julia 中的 HDF5 存储中的表作为数据框