2016-09-19 2 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'は何にでも作用するので、' enumerable'プロトコルと 'concat'コストのパターンマッチングを実装します。 – mudasobwa

+1

はい、しかし、私はそれが4k倍遅くなるとは思わない...おそらくベンチマークはかなりappositeではない。 –

+1

'List#++/2'は文字通り何も費やさないので、4k倍遅くなります。パターンマッチングを比較する可能性がありますが、そのコストは[もちろんではありません] 'noop'と比較されます。 – mudasobwa

答えて

14

短い回答:Constant Folding

長い答え:Elixirがbeamファイルにコンパイルされている場合、Elixirのモジュール属性はリテラル値に置き換えられます。たとえば、次のコード:一定の折りたたみの最適化、evaluates ++ operations as much as possible at compile timeを行い

-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]. 

アーランコンパイラのsys_core_foldモジュール、:

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 

はにコンパイルされます。この場合、両方のリストはリテラルなので、関数呼び出しを完全に排除して結果リストに置き換えることができます。したがって、ベンチマークでは、++関数はVMに既に存在するリストを返すだけです。それは(も3に折りたたまれた定数である)として、高速1 + 2を行うようだ:

... 
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

ありがとうございます。それは説明する –

関連する問題