20012010
Ericsson AB. All Rights Reserved.
The contents of this file are subject to the Erlang Public License,
Version 1.1, (the "License"); you may not use this file except in
compliance with the License. You should have received a copy of the
Erlang Public License along with this software. If not, it can be
retrieved online at http://www.erlang.org/.
Software distributed under the License is distributed on an "AS IS"
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
the License for the specific language governing rights and limitations
under the License.
Common Caveats
Bjorn Gustavsson
2001-08-08
commoncaveats.xml
Here we list a few modules and BIFs to watch out for, and not only
from a performance point of view.
The regexp module
The regular expression functions in the
regexp
module are written in Erlang, not in C, and were
meant for occasional use on small amounts of data,
for instance for validation of configuration files
when starting an application.
Use the re module
(introduced in R13A) instead, especially in time-critical code.
The timer module
Creating timers using erlang:send_after/3
and erlang:start_timer/3
is much more efficient than using the timers provided by the
timer module. The
timer module uses a separate process to manage the timers,
and that process can easily become overloaded if many processes
create and cancel timers frequently (especially when using the
SMP emulator).
The functions in the timer module that do not manage timers (such as
timer:tc/3 or timer:sleep/1), do not call the timer-server process
and are therefore harmless.
list_to_atom/1
Atoms are not garbage-collected. Once an atom is created, it will never
be removed. The emulator will terminate if the limit for the number
of atoms (1048576 by default) is reached.
Therefore, converting arbitrary input strings to atoms could be
dangerous in a system that will run continuously.
If only certain well-defined atoms are allowed as input, you can use
list_to_existing_atom/1
to guard against a denial-of-service attack. (All atoms that are allowed
must have been created earlier, for instance by simply using all of them
in a module and loading that module.)
Using list_to_atom/1 to construct an atom that is passed to
apply/3 like this
apply(list_to_atom("some_prefix"++Var), foo, Args)
is quite expensive and is not recommended in time-critical code.
length/1
The time for calculating the length of a list is proportional to the
length of the list, as opposed to tuple_size/1, byte_size/1,
and bit_size/1, which all execute in constant time.
Normally you don't have to worry about the speed of length/1,
because it is efficiently implemented in C. In time critical-code, though,
you might want to avoid it if the input list could potentially be very long.
Some uses of length/1 can be replaced by matching.
For instance, this code
foo(L) when length(L) >= 3 ->
...
can be rewritten to
foo([_,_,_|_]=L) ->
...
(One slight difference is that length(L) will fail if the L
is an improper list, while the pattern in the second code fragment will
accept an improper list.)
setelement/3
setelement/3
copies the tuple it modifies. Therefore, updating a tuple in a loop
using setelement/3 will create a new copy of the tuple every time.
There is one exception to the rule that the tuple is copied.
If the compiler clearly can see that destructively updating the tuple would
give exactly the same result as if the tuple was copied, the call to
setelement/3 will be replaced with a special destructive setelement
instruction. In the following code sequence
multiple_setelement(T0) ->
T1 = setelement(9, T0, bar),
T2 = setelement(7, T1, foobar),
setelement(5, T2, new_value).
the first setelement/3 call will copy the tuple and modify the
ninth element. The two following setelement/3 calls will modify
the tuple in place.
For the optimization to be applied, all of the followings conditions
must be true:
- The indices must be integer literals, not variables or expressions.
- The indices must be given in descending order.
- There must be no calls to other function in between the calls to
setelement/3.
- The tuple returned from one setelement/3 call must only be used
in the subsequent call to setelement/3.
If it is not possible to structure the code as in the multiple_setelement/1
example, the best way to modify multiple elements in a large tuple is to
convert the tuple to a list, modify the list, and convert the list back to
a tuple.
size/1
size/1 returns the size for both tuples and binary.
Using the new BIFs tuple_size/1 and byte_size/1 introduced
in R12B gives the compiler and run-time system more opportunities for
optimization. A further advantage is that the new BIFs could help Dialyzer
find more bugs in your program.
split_binary/2
It is usually more efficient to split a binary using matching
instead of calling the split_binary/2 function.
Furthermore, mixing bit syntax matching and split_binary/2
may prevent some optimizations of bit syntax matching.
DO
> = Bin,]]>
DO NOT
{Bin1,Bin2} = split_binary(Bin, Num)
The '--' operator
Note that the '--' operator has a complexity
proportional to the product of the length of its operands,
meaning that it will be very slow if both of its operands
are long lists:
DO NOT
Instead use the ordsets
module:
DO
HugeSet1 = ordsets:from_list(HugeList1),
HugeSet2 = ordsets:from_list(HugeList2),
ordsets:subtract(HugeSet1, HugeSet2)
Obviously, that code will not work if the original order
of the list is important. If the order of the list must be
preserved, do like this:
DO
Subtle note 1: This code behaves differently from '--'
if the lists contain duplicate elements. (One occurrence
of an element in HugeList2 will remove all
occurrences in HugeList1.)
Subtle note 2: This code compares lists elements using the
'==' operator, while '--' uses the '=:='. If
that difference is important, sets can be used instead of
gb_sets, but note that sets:from_list/1 is much
slower than gb_sets:from_list/1 for long lists.
Using the '--' operator to delete an element
from a list is not a performance problem:
OK
HugeList1 -- [Element]