2016-09-19 77 views
10

我试着用Benchfella做一些快速的标杆:为什么在连接列表时Enum.concat比++慢得多?

defmodule ConcatListBench do 
    use Benchfella 

    @a1 Enum.to_list(1..10_000) 
    @a2 Enum.to_list(10_000..20_0000) 

    bench "++" do 
    @a1 ++ @a2 
    end 

    bench "Enum.concat" do 
    Enum.concat(@a1, @a2) 
    end 
end 

,当运行它:

$ elixir -v 
Erlang/OTP 19 [erts-8.0.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false] [dtrace] 
Elixir 1.4.0-dev (762e7de) 

$ mix bench 
Settings: 
    duration:  1.0 s 

## ConcatListBench 
[10:01:09] 1/2: ++ 
[10:01:20] 2/2: Enum.concat 

Finished in 14.03 seconds 

## ConcatListBench 
benchmark na iterations average time 
++   1000000000 0.01 µs/op 
Enum.concat  50000 45.03 µs/op 

的问题是如何Enum.concat可能是内部较慢(超过4K倍),如果it uses++运营商列表?

据我所知,Enum.concat中的守卫子句和模式匹配花费了一段时间,但基准测试显示了很大的差异,不是吗?

UPDATE:这种情况使用++在编译时优化由于Constant Folding,级联和需要来运行即时时间。所以基准不太现实。

+1

因为'Enum.concat'可以用于任何事情,在'concat'成本中实现'Enumerable'协议和模式匹配。 – mudasobwa

+1

是的,但我不认为它会慢4k倍...也许基准测试不太合适。 –

+1

因为'List#++/2'几乎没有成本,所以它慢了4k倍。您可能会比较模式匹配,这会造成[当然不是]'noop'的成本。 – mudasobwa

回答

14

简短回答:Constant Folding

较长的答案:当Elixir编译为beam文件时,Elixir中的模块属性被替换为它们的文字值。例如,下列代码:

defmodule ConcatListBench do 
    @a1 Enum.to_list(1..10) 
    @a2 Enum.to_list(10..20) 

    def plusplus, do: @a1 ++ @a2 

    def concat, do: Enum.concat(@a1, @a2) 
end 

编译为:

-module('Elixir.ConcatListBench'). 
... 
concat() -> 
    'Elixir.Enum':concat([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 
      [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]). 

plusplus() -> 
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ++ 
     [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]. 

Erlang的编译器的sys_core_fold模块,它不恒定折叠优化,evaluates ++ operations as much as possible at compile time。由于在这种情况下,这两个列表都是文字,它可以完全消除函数调用并将其替换为结果列表。因此,在您的基准测试中,++函数只是返回VM中已经存在的列表。它以最快的速度做1 + 2(这也是恒定的折叠3):

... 
bench "1 + 2" do 
    1 + 2 
end 
... 
## ConcatListBench 
benchmark na iterations average time 
1 + 2  1000000000 0.01 µs/op 
++   1000000000 0.01 µs/op 
Enum.concat  50000 37.89 µs/op 

一个更现实的基准是做一个间接调用++这Erlang的编译器不折:

def plus_plus(a, b), do: a ++ b 

bench "++" do 
    plus_plus(@a1, @a2) 
end 

这些是3次运行的输出:

## ConcatListBench 
benchmark na iterations average time 
Enum.concat  50000 37.44 µs/op 
++    50000 41.65 µs/op 

## ConcatListBench 
benchmark na iterations average time 
++    50000 36.07 µs/op 
Enum.concat  50000 38.58 µs/op 

## ConcatListBench 
benchmark na iterations average time 
Enum.concat  50000 39.34 µs/op 
++    50000 40.74 µs/op 

因此,如果你的列表在编译时不是常量,那么两种方法都是一样的快。我期望Enum.concat慢一点(特别是对于小列表),因为它比++做的多一点。

+0

谢谢。这解释了 –

相关问题