是否有用于从 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 的任务,至少据我所知。然而,它们似乎也比纯迭代器慢得多。例如,如果您可以使用 imap
、chain
等简单的迭代器(由 Iterators.jl
包提供)来表达您的函数,这似乎是非常可取的。
理论上是否有可能在 julia 中构建一个将生成器风格的函数转换为灵活的 fast 迭代器的宏?
Extra-Point-Question:如果可能的话,是否有一个内联此类迭代器的通用宏?
【问题讨论】:
Channel
s,在 0.6 中替换 Task
s,应该比 Task
s 快得多。
我还没有听说过它们,并且我阅读了很多关于 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 中是不是存在类似 Python 的 virtualenv?