Is ++ operator more expensive than | operator in E

2019-06-25 14:21发布

问题:

I was reading Learn You Some Erlang and I came upon this example in the Recursion chapter.

tail_sublist(_, 0, SubList) -> SubList;
tail_sublist([], _, SubList) -> SubList;
tail_sublist([H|T], N, SubList) when N > 0 -> 
tail_sublist(T, N-1, [H|SubList]).

As the author goes on to explain that there is a fatal flaw in our code. It being that the sub lists hence produced would be reverse and we would have to re-reverse them to get the correct output. In contrast, what I did was use the ++ operator to avoid reversing the list later.

sublist_tail([],_,Acc) -> Acc;
sublist_tail(_,0,Acc) -> Acc;
sublist_tail([H|T],N,Acc) -> sublist_tail(T,N-1,Acc++[H]).

My question is, is the ++ operator more expensive than the | operator? And if it is, would my solution (using ++ operator) still be slow compared to the author's solution (including reversing the list to get the correct output)?

回答1:

is the ++ operator more expensive than the | operator?

That depends. If you use it correctly, then no. ++ is only dangerous when you have a big left-hand-side operand.

Each time a "++"-operator is invoked on a left-hand List (like: List1 ++ List2), you are creating a new List, that is a copy of your left-hand operand (List1). Each copy operation then has a runtime, that is dependent on the length of your List1 (which keeps growing with your iterations).

So, if you prepend your values 'head first', you don't have to perform a copy-operation over the whole list in each step. This also means, accumulation with ++ at the head of the List wouldn't be so bad either, since only the "H"-value is copied once in each iteration:

sublist_tail([H|T],N,Acc) -> sublist_tail(T,N-1,[H]++Acc).

But if you are already accumulating head-first (and thus have to reverse later anyhow), you can do it with the cons-operator (|)

sublist_tail([H|T],N,Acc) -> sublist_tail(T,N-1,[H|Acc]).

This is the 'proper' way, since (please correct me if I am wrong) ++ is only syntactic sugar and is implemented internally with a cons-operator (|).



回答2:

You might want to read about this issue in the Erlang efficiency guide, since there it says that building the list via | and then reversing the result is more efficient than using the appending ++ operator. If you want to know the performance difference, use timer:tc:

1> timer:tc(fun() -> lists:reverse(lists:foldl(fun(V, Acc) -> [V|Acc] end, [], lists:seq(1,1000))) end).
{1627,
 [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
  23,24,25,26,27|...]}
2> timer:tc(fun() -> lists:foldl(fun(V, Acc) -> Acc++[V] end, [], lists:seq(1,1000)) end).
{6216,
 [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,
  23,24,25,26,27|...]}

Both approaches create lists of 1000 integers, but these measurements based on Erlang/OTP 17.5 show the prepending/reversing version is roughly 4x faster than the appending version (YMMV of course).



标签: erlang