Elixir 为什么Enum.concat比++;在连接列表时?

Elixir 为什么Enum.concat比++;在连接列表时?,elixir,benchmarking,Elixir,Benchmarking,我尝试使用以下工具进行一些快速基准测试: defmodule concatistbench do 使用Benchfella @a1枚举到列表(1..10_000) @a2清单清单(10万至20万) 工作台“+”do @a1++@a2 结束 工作台“Enum.concat”do 枚举concat(@a1,@a2) 结束 结束 在运行时: $ elixir -v Erlang/OTP 19 [erts-8.0.2] [source] [64-bit] [smp:4:4] [async-thread

我尝试使用以下工具进行一些快速基准测试:

defmodule concatistbench do
使用Benchfella
@a1枚举到列表(1..10_000)
@a2清单清单(10万至20万)
工作台“+”do
@a1++@a2
结束
工作台“Enum.concat”do
枚举concat(@a1,@a2)
结束
结束
在运行时:

$ 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倍)

我知道
Enum.concat
中的保护子句和模式匹配需要花费一些时间,但基准测试显示了很大的不同,不是吗

更新:发生这种情况的原因是,使用编译时优化的
++
进行连接,并需要即时运行。因此,基准不太现实。

简短回答:

更详细的回答:当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编译器的模块,它执行常量折叠优化。因为在这种情况下,两个列表都是文本,所以它可以完全消除函数调用并用结果列表替换它。因此,在您的基准测试中,
++
函数只是返回一个已经存在于VM中的列表。它的速度与执行
1+2
的速度一样快(这也是不断折叠到
3
):

更现实的基准测试是对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
会稍微慢一点(特别是对于小列表),因为它比
++

做的工作要多一些,因为
Enum.concat
可以处理任何事情,在
concat
中实现
可枚举的
协议和模式匹配是的,但我不希望它慢4k倍。。。也许基准测试并不合适。它的速度慢了4k倍,因为
List++/2
几乎不需要任何成本。您可能会将模式匹配与[当然不完全是]
noop
进行比较。谢谢。这就解释了
def plus_plus(a, b), do: a ++ b

bench "++" do
  plus_plus(@a1, @a2)
end
## 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