20012017
Ericsson AB. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Common Caveats
Bjorn Gustavsson
2001-08-08
commoncaveats.xml
This section lists a few modules and BIFs to watch out for, not only
from a performance point of view.
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 in STDLIB.
The timer module uses a separate process to manage the timers.
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 is never
removed. The emulator terminates if the limit for the number
of atoms (1,048,576 by default) is reached.
Therefore, converting arbitrary input strings to atoms can be
dangerous in a system that runs continuously.
If only certain well-defined atoms are allowed as input,
list_to_existing_atom/1
can be used to
to guard against a denial-of-service attack. (All atoms that are allowed
must have been created earlier, for example, 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 as follows, is quite expensive and not recommended
in time-critical code:
apply(list_to_atom("some_prefix"++Var), foo, Args)
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, there is no need to worry about the speed of length/1,
because it is efficiently implemented in C. In time-critical code,
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 example, the following code:
foo(L) when length(L) >= 3 ->
...
can be rewritten to:
foo([_,_,_|_]=L) ->
...
One slight difference is that length(L) fails if L
is an improper list, while the pattern in the second code fragment
accepts an improper list.
setelement/3
setelement/3
copies the tuple it modifies. Therefore, updating a tuple in a loop
using setelement/3 creates 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 the same result as if the tuple was copied, the call to
setelement/3 is replaced with a special destructive setelement
instruction. In the following code sequence, the first setelement/3
call copies the tuple and modifies the ninth element:
multiple_setelement(T0) ->
T1 = setelement(9, T0, bar),
T2 = setelement(7, T1, foobar),
setelement(5, T2, new_value).
The two following setelement/3 calls modify
the tuple in place.
For the optimization to be applied, all 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 another 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 the code cannot be structured 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 it back to
a tuple.
size/1
size/1 returns the size for both tuples and binaries.
Using the BIFs tuple_size/1 and byte_size/1
gives the compiler and the runtime system more opportunities for
optimization. Another advantage is that the BIFs give Dialyzer more
type information.
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
can prevent some optimizations of bit syntax matching.
DO
> = Bin,]]>
DO NOT
{Bin1,Bin2} = split_binary(Bin, Num)
Operator "--"
The "--" operator has a complexity
proportional to the product of the length of its operands.
This means that the operator is very slow if both of its operands
are long lists:
DO NOT
Instead use the ordsets
module in STDLIB:
DO
HugeSet1 = ordsets:from_list(HugeList1),
HugeSet2 = ordsets:from_list(HugeList2),
ordsets:subtract(HugeSet1, HugeSet2)
Obviously, that code does not work if the original order
of the list is important. If the order of the list must be
preserved, do as follows:
DO
This code behaves differently from "--"
if the lists contain duplicate elements (one occurrence
of an element in HugeList2 removes all
occurrences in HugeList1.)
Also, this code compares lists elements using the
"==" operator, while "--" uses the "=:=" operator.
If that difference is important, sets can be used instead of
gb_sets, but 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]