A note on efficiency: OO is not inefficient
Signal processing software applications have traditionally been developed
under the structured programming paradigm and more precisely in C
language. In many DSP applications, specially in embedded systems
where the final code is in assembly language, the program efficiency
both in execution speed and size is one of the most important factors.
It is still not strange finding that some algorithms are implemented
directly in assembler. All the benefits attributed to object-orientation
are many times put aside with the intent of building something ``fast''
and ``light''.
In this context, it is of course difficult to convince signal processing
engineers that object-oriented (and in fact any sort of code structuring
that focuses on reusability and understandability) is worthwhile.
In the course of the CLAM framework development (see chapter 3)
we have had the opportunity to convince many different people that
well-structured object-oriented code is not inefficient and can indeed
yield more efficient and far more robust applications (see [de Champeaux et al., 1993],
for instance).
At this point though the choice of an appropriate object-oriented
programming language is very important. Typically languages such as
Java, running on a virtual machine, cannot yield efficient code. It
is also important to be able to manage low-level issues like memory
allocation policies whenever necessary. The choice of C++ as the most
appropriate object-oriented language comes naturally, even more when
C++ is already a de facto standard for DSP applications and a natural
follow-up of the C programming language. It may be argued that C++
is not a ``true'' object-oriented language, even its creator advertises
it as a multiparadigm language [Stroustrup, 1995]. Nevertheless
this language can be effectively be used to build a completely object-oriented
framework, abandoning the paradigm only for strictly necessary low-level
issues such as hardware access or memory handling but hiding these
details in such a way that the user does not even need to be aware
they exist.
But the choice of an object-oriented language does not guarantee the
quality of the resulting code. Many signal processing applications
are in fact written in C++ in its ``a better C'' meaning, forgetting
about all the advantages and tools offered by the object-oriented
paradigm. A non-exhaustive list of common FUDs about object oriented
efficiency, particularized to the case of the C++ language, are summarized
in the following1.2:
- Encapsulation is inefficient: Some signal processing developers
argue that the indirection introduced by adding a Set or Get operation
to an attribute yields a less efficient executable. Because of this
they consciously break the encapsulation principle by making all attributes
public. The disadvantages of doing so are as important as the mixing-up
of state and behavior, the existence of a non-homogeneous interface,
or the lack of an implementation that can evolve independently from
its interface. On the other hand it is definitely not true that the
introduction of an operation call introduces inefficiency. In C++
an operation can be ``inlined'' so the call of an operation does
not introduce a memory indirection. Although this is only feasible
in methods with very little computation time and space requirements,
this is exactly what we face when implementing a Getter or a Setter.
- Modularity is inefficient: Clearly a memory indirection introduces
some overhead that at the end may result in a less efficient final
application. But, as already mentioned, this is only so whenever inlining
is not feasible. And inlining is not possible when the operation executed
in the method is so complex that the time of its execution is several
orders of magnitude greater than the time taken for the memory indirection.
In this case it is also clear that the overhead of the indirection
can be neglected and is by far surpassed by the benefits introduced,
which will be later commented.
- Inheritance is inefficient: When declaring a base class with
a virtual operation (needed in any case in order to implement polymorphism)
all objects instance of any of its subclasses will have a virtual
function pointer table. This produces memory inefficiency as objects
will occupy more memory than the strictly necessary for storing their
attributes. Furthermore, whenever a virtual operation is invoked on
a pointer or reference, dynamic binding is introduced and therefore
a new indirection that will add inefficiency. Also, virtual operations
cannot be inlined. The solution is to treat inheritance with care
and not introduce virtuality on any method that could be inlined (i.e.
the cost of an indirection is comparable to the cost of the method
itself). On the other hand, inheritance, just as modularity, introduces
far more benefits than inconveniences.
Although these previous and other related misbelieves can, as already
commented, be minimized the main benefit of object-orientation can
be better understood in the mid-term. When building a well-structured
system we are not only worrying about short-term efficiency issues
as the ones commented but we are also setting the grounds for further
refactorings in order to improve overall efficiency. In a well-structured
system it is much easier to detect efficiency bugs and treat them
in a correct way. The clearer, more modular and well-structured the
code is the easier it is to optimize.
We will better understand all these issues in the following example.
Subsections
2004-10-18