• Aucun résultat trouvé

The Influence of Optimization

Part 2 The Software Side: Disappointments and

6 Implications of Compiler and Systems Issues

6.7 The Influence of Optimization

Techniques employed by optimizing compilers can result in unexpected behavior of a program in several ways. One is the interplay of an optimiza-tion step with a specific code. The other is the lazy evaluaoptimiza-tion of expressions.

6.7.1 Interference with Specific Statements

The more details a program specifies, the less an optimizing compiler can do with it to improve its performance, because optimizing compilers tend to be extremely conservative. Therefore, if it appears that the programmer really wanted a particular formulation, optimizing compilers tend to leave it alone. If it is clear that the programmer expressed a general operation, it is up to the compiler to optimize that operation according to the target

C6730_C006.fm  Page 159  Friday, August 11, 2006  9:21 AM

160 A Programmer’s Companion to Algorithm Analysis platform. This is an important notion. While not every platform is amenable to certain optimizations, this should not be the programmer’s concern; the compiler should ensure that the best possible (or at least a very good) version be executed. For example, if a programmer were able state that she wanted to multiply the matrices A and B of size [1:n,1:n] and store the result in C, it would be up to the optimizing compiler to select the best possible approach. If n is small, the standard three nested loops might be appropriate, but if n is large, a more efficient algorithm might be employed (see Chapter 3). Furthermore, if the matrices do not fit into main memory, an appropriate memory mapping might be selected by the compiler to minimize the transfer of blocks between main memory and disk.

While most programming languages do not provide the ability of speci-fying operations very loosely, this example indicates that the programmer should not attempt to be too clever. Trying to program to a specific platform is generally highly undesirable. It sacrifices the portability of a program written in a higher-level language (as opposed to assembly language or machine code) and it may render a highly tuned program less efficient since it interferes with optimization. Programmers should firmly keep in mind that today’s optimization compilers tend to produce code that is much more efficient than most hand-tuned code — provided one lets the optimizing compiler do what it is supposed to do. The less detail the programmer stipulates in the code, the more likely it is that the optimizing compiler will attain its goal of producing efficient code. Finally, highly tuned programs tend to be unmaintainable since a relatively simple idea in them is transmo-grified into something that frequently looks quite bizarre.

Programmers should also be aware of the importance of software porta-bility in this context. This is very pointedly illustrated by the following observation: Hardware tends to be obsolete in 5 to 10 years, often even faster;

software, however, is much longer-lived. As a result, much of the software executing on today’s systems was written long before these hardware plat-forms existed. Since software portability is reduced by a tendency to fine-tune programs, this temptation should be firmly resisted.

A good rule of thumb is to formulate an operation in as simple a form as possible and leave the optimization of the simple idea to the optimizing compiler.

6.7.2 Lazy Evaluation

A particular instance of optimization that can generate surprises for pro-grammers is the lazy evaluation of expressions. The general principle is that the evaluation of an expression proceeds inside out, starting with the sim-plest subexpression and composing from these values more complicated subexpressions until the final value of the entire expression is determined.

Lazy evaluation is based on the observation that knowledge of the value of a certain subexpression may permit knowledge of the final value, without

C6730_C006.fm  Page 160  Friday, August 11, 2006  9:21 AM

Implications of Compiler and Systems Issues for Software 161 the evaluation of other subexpressions. For example, knowing that one factor of a product has the value 0 permits one to conclude that final value of the product is also 0, without evaluating the second factor.

Lazy evaluation is particularly important for boolean expressions. For example, the and of two expressions is false if one of the two is false;

similarly, the or of two expressions is true is one of the two is true. In both cases it is not necessary to evaluate the other expression.

Lazy evaluation of expressions tends to be unnoticed if there are no side effects — instances where the evaluation of an expression not only provides a value but carries out other operations as well. For example, consider the product of two values. If these two values are function calls that execute print statements in addition to returning a value, whether or not each of the functions is executed is quite important. If a function is evaluated, it carries out the print statements, but if it is not, then it does not print. Thus, a programmer may expect certain actions to occur because the expression is evaluated, without realizing that in lazy evaluation, not every component of an expression is evaluated.

A particular side effect could be the indication that a particular variable does not have a value. This would be the case if the language recognizes that that variable is undefined (see the discussion in Section 6.6). One can easily see that lazy evaluation can defeat the purpose of this (safe) approach to initializing variables. A programmer may infer that all variables are defined since the entire expression was evaluated, but in lazy evaluation obtaining a value for an expression does not mean that every variable in that expression had been inspected. Thus, it is entirely possible to execute all statements of a loop 100 times successfully, only to be told in the 101st iteration that a particular variable in one of the statements is undefined.

Lazy evaluation can be easily defeated by the programmer by breaking up expressions. For example, instead of testing whether

(F(j)>10.0) or (F(j+1)<0.0)

is true, we could write the two assignments b1:=(F(j)>10.0); b1:=(F(j+1)<0.0)

and then test whether b1 or b2

is true. In this way, both function calls are executed since they are needed to assign values to the two boolean variables b1 and b2.

C6730_C006.fm  Page 161  Friday, August 11, 2006  9:21 AM

162 A Programmer’s Companion to Algorithm Analysis