Erlang -- List Handling

User's Guide
PDF
Top

Efficiency Guide
User's Guide
Version 8.1

Expand All
Contract All

Chapters

- Introduction
  - Top of chapter
  - Purpose
  - Prerequisites
- The Eight Myths of Erlang Performance
  - Top of chapter
  - Myth: Funs are Slow
  - Myth: List Comprehensions are Slow
  - Myth: Tail-Recursive Functions are Much Faster Than Recursive Functions
  - Myth: Operator "++" is Always Bad
  - Myth: Strings are Slow
  - Myth: Repairing a Dets File is Very Slow
  - Myth: BEAM is a Stack-Based Byte-Code Virtual Machine (and Therefore Slow)
  - Myth: Use "_" to Speed Up Your Program When a Variable is Not Used
- Common Caveats
  - Top of chapter
  - Timer Module
  - list_to_atom/1
  - length/1
  - setelement/3
  - size/1
  - split_binary/2
  - Operator "--"
- Constructing and Matching Binaries
  - Top of chapter
  - How Binaries are Implemented
  - Constructing Binaries
  - Matching Binaries
- List Handling
  - Top of chapter
  - Creating a List
  - List Comprehensions
  - Deep and Flat Lists
  - Recursive List Functions
- Functions
  - Top of chapter
  - Pattern Matching
  - Function Calls
  - Memory Usage in Recursion
- Tables and Databases
  - Top of chapter
  - Ets, Dets, and Mnesia
  - Ets-Specific
  - Mnesia-Specific
- Processes
  - Top of chapter
  - Creating an Erlang Process
  - Process Messages
  - SMP Emulator
- Drivers
  - Top of chapter
  - Drivers and Concurrency
  - Avoiding Copying Binaries When Calling a Driver
  - Returning Small Binaries from a Driver
  - Returning Large Binaries without Copying from a Driver
- Advanced
  - Top of chapter
  - Memory
  - System Limits
- Profiling
  - Top of chapter
  - Do Not Guess About Performance - Profile
  - Large Systems
  - What to Look For
  - Tools
  - Benchmarking

# 5 List Handling

### 5.1 Creating a List

Lists can only be built starting from the end and attaching list elements at the beginning. If you use the "++" operator as follows, a new list is created that is a copy of the elements in List1, followed by List2:

    List1 ++ List2

Looking at how lists:append/1 or ++ would be implemented in plain Erlang, clearly the first list is copied:

    append([H|T], Tail) ->
        [H|append(T, Tail)];
    append([], Tail) ->
        Tail.

When recursing and building a list, it is important to ensure that you attach the new elements to the beginning of the list. In this way, you will build one list, not hundreds or thousands of copies of the growing result list.

Let us first see how it is not to be done:

DO NOT

    bad_fib(N) ->
        bad_fib(N, 0, 1, []).

    bad_fib(0, _Current, _Next, Fibs) ->
        Fibs;
    bad_fib(N, Current, Next, Fibs) ->
        bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

Here more than one list is built. In each iteration step a new list is created that is one element longer than the new previous list.

To avoid copying the result in each iteration, build the list in reverse order and reverse the list when you are done:

DO

    tail_recursive_fib(N) ->
        tail_recursive_fib(N, 0, 1, []).

    tail_recursive_fib(0, _Current, _Next, Fibs) ->
        lists:reverse(Fibs);
    tail_recursive_fib(N, Current, Next, Fibs) ->
        tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

### 5.2 List Comprehensions

Lists comprehensions still have a reputation for being slow. They used to be implemented using funs, which used to be slow.

In recent Erlang/OTP releases (including R12B), a list comprehension:

    [Expr(E) || E <- List]

is basically translated to a local function:

    'lc^0'([E|Tail], Expr) ->
        [Expr(E)|'lc^0'(Tail, Expr)];
    'lc^0'([], _Expr) -> [].

In R12B, if the result of the list comprehension will obviously not be used, a list will not be constructed. For example, in this code:

    [io:put_chars(E) || E <- List],
    ok.

or in this code:

    ...
    case Var of
        ... ->
            [io:put_chars(E) || E <- List];
        ... ->
    end,
    some_function(...),
    ...

the value is not assigned to a variable, not passed to another function, and not returned. This means that there is no need to construct a list and the compiler will simplify the code for the list comprehension to:

    'lc^0'([E|Tail], Expr) ->
        Expr(E),
        'lc^0'(Tail, Expr);
    'lc^0'([], _Expr) -> [].

### 5.3 Deep and Flat Lists

lists:flatten/1 builds an entirely new list. It is therefore expensive, and even more expensive than the ++ operator (which copies its left argument, but not its right argument).

In the following situations, you can easily avoid calling lists:flatten/1:

- When sending data to a port. Ports understand deep lists so there is no reason to flatten the list before sending it to the port.
- When calling BIFs that accept deep lists, such as list_to_binary/1 or iolist_to_binary/1.
- When you know that your list is only one level deep, you can use lists:append/1.

#### Port Example

DO

          ...
          port_command(Port, DeepList)
          ...

DO NOT

          ...
          port_command(Port, lists:flatten(DeepList))
          ...

A common way to send a zero-terminated string to a port is the following:

DO NOT

          ...
          TerminatedStr = String ++ [0], % String="foo" => [$f, $o, $o, 0]
          port_command(Port, TerminatedStr)
          ...

Instead:

DO

          ...
          TerminatedStr = [String, 0], % String="foo" => [[$f, $o, $o], 0]
          port_command(Port, TerminatedStr)
          ...

#### Append Example

DO

          > lists:append([[1], [2], [3]]).
          [1,2,3]
          >

DO NOT

          > lists:flatten([[1], [2], [3]]).
          [1,2,3]
          >

### 5.4 Recursive List Functions

In Section 7.2, the following myth was exposed: Tail-Recursive Functions are Much Faster Than Recursive Functions.

To summarize, in R12B there is usually not much difference between a body-recursive list function and tail-recursive function that reverses the list at the end. Therefore, concentrate on writing beautiful code and forget about the performance of your list functions. In the time-critical parts of your code (and only there), measure before rewriting your code.

Note

This section is about list functions that construct lists. A tail-recursive function that does not construct a list runs in constant space, while the corresponding body-recursive function uses stack space proportional to the length of the list.

For example, a function that sums a list of integers, is not to be written as follows:

DO NOT

    recursive_sum([H|T]) -> H+recursive_sum(T);
    recursive_sum([])    -> 0.

Instead:

DO

    sum(L) -> sum(L, 0).

    sum([H|T], Sum) -> sum(T, Sum + H);
    sum([], Sum)    -> Sum.