Julia中是否有懒惰的 `filter` 函数?

5
在Python中,可以在列表推导式中使用if来筛选出元素。在Julia中是否有类似于lazy filter的等效功能?
for x in filter(x->x<2, 1:3)
println(x)
end

这段代码仅返回并打印出 1,但调用 filter(x->x<2, 1:3) 函数时会立即执行,因此在处理数十亿条记录时可能不太理想。

1个回答

9
你可以像在Python中一样做这件事:
julia> function f()
           for x in (i for i in 1:10^9 if i == 10^9)
               println(x)
           end
       end
f (generic function with 1 method)

julia> @time f()
1000000000
  3.293702 seconds (139.87 k allocations: 7.107 MiB)

julia> @time f()
1000000000
  3.224707 seconds (11 allocations: 352 bytes)

你会发现它并没有分配内存。但是,不使用生成器,在循环内部执行过滤测试更快:

julia> function g()
           for x in 1:10^9
               x == 10^9 && println(x)
           end
       end
g (generic function with 1 method)

julia> @time g()
1000000000
  2.098305 seconds (53.49 k allocations: 2.894 MiB)

julia> @time g()
1000000000
  2.094018 seconds (11 allocations: 352 bytes)

编辑 最后,您可以使用Iterators.filter

julia> function h()
          for x in Iterators.filter(==(10^9), 1:10^9)
              println(x)
          end
       end
h (generic function with 1 method)

julia>

julia> @time h()
1000000000
  0.390966 seconds (127.96 k allocations: 6.599 MiB)

julia> @time h()
1000000000
  0.311650 seconds (12 allocations: 688 bytes)

在这种情况下,最快的方法是使用迭代器工具(请参见https://docs.julialang.org/en/latest/base/iterators/#Iteration-utilities-1)。
您还可以查看https://github.com/JuliaCollections/IterTools.jl编辑2 有时候Julia比你想象的强大。看看这个:
julia> function g2()
          for x in 1:1_000_000_000
              x == 1_000_000_000 && println(x)
          end
       end
g2 (generic function with 1 method)

julia>

julia> @time g2()
1000000000
  0.029332 seconds (62.91 k allocations: 3.244 MiB)

julia> @time g2()
1000000000
  0.000636 seconds (11 allocations: 352 bytes)

我们可以看到编译器已经基本上将我们所有的计算都编译出来了。

本质上,在之前的例子中,常数传播生效并用1_000_000_000替换了10 ^ 9Iterators.filter的示例中。

因此,我们必须设计一个更智能的测试。下面是它:

julia> using BenchmarkTools

julia> function f_rand(x)
           s = 0.0
           for v in (v for v in x if 0.1 < v < 0.2)
               s += v
           end
           s
       end
f_rand (generic function with 1 method)

julia> function g_rand(x)
           s = 0.0
           for v in x
               if 0.1 < v < 0.2
                   s += v
               end
           end
           s
       end
g_rand (generic function with 1 method)

julia> function h_rand(x)
           s = 0.0
           for v in Iterators.filter(v -> 0.1 < v < 0.2, x)
               s += v
           end
           s
       end
h_rand (generic function with 1 method)

julia> x = rand(10^6);

julia> @btime f_rand($x)
  2.032 ms (0 allocations: 0 bytes)
14922.291597613703

julia> @btime g_rand($x)
  1.804 ms (0 allocations: 0 bytes)
14922.291597613703

julia> @btime h_rand($x)
  2.035 ms (0 allocations: 0 bytes)
14922.291597613703

现在我们得到了我最初期望的结果(一个带有 if 的简单循环是最快的)。


2
有任何想法为什么 Iterator.filter 如此之快? - Krastanov
2
啊 - 编译器优化开始生效了。我会在答案中详细解释它们。 - Bogumił Kamiński
我被 for x in (i for i in 1:10^9 if i == 10^9) 给难住了,我原以为这样写可以 for x for i in 1:10^9 if i == 10^9; #do stuff; end。但是这个写法真的很棒! - xiaodai
“现在我们得到了我最初期望的结果(一个简单的循环和if语句是最快的)。”虽然这些数字非常接近,但这很有趣。你确定差异在统计学上是显著的吗? - Nathan Davis
你可以进行基准测试 :), 并改变 x 的大小(不同的硬件可能会给出稍微不同的结果),但根据我的测试 g_rand 将是最快的,f_randh_rand 是相当的(对于大的 xh_rand 似乎要快一些)。 - Bogumił Kamiński

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接