Ответ 1
Короткий ответ: Постоянная сгибание.
Более длинный ответ: атрибуты модуля в Elixir заменяются литеральными значениями, когда Elixir скомпилирован в файлы beam
. Например, следующий код:
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
, который выполняет постоянную оптимизацию сгибания, оценивает операции ++
как можно больше во время компиляции. Поскольку в этом случае оба списка являются литералами, он может полностью исключить вызов функции и заменить его на полученный список. Поэтому в вашем тесте функция ++
просто возвращает список, который уже существует в виртуальной машине. Это выполняется так же быстро, как и выполнение 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
будет немного медленнее (особенно для небольших списков), так как он немного работает больше, чем ++
.