2015-10-08 07:17:25 UTC
Apart from trivial optimisations to avoid unnecessary register loads, which is something many modern compilers do, there is one important aspect in connection with optimisation that would have a very significant impact but most languages and compilers have failed to make any headway whatsoever:
How do you counter the widespread culture of premature optimisation amongst practitioners?
Many people may not even recognise premature optimisation as a problem. We believe it is a huge problem that contributes very significantly to the dismal status of software. Premature optimisation directly runs counter to correctness, reliability and safety of software. But even celebrity hackers such as RMS are having a hard time to convince the lemmings that premature optimisation is bad.
The underlying root causes are attitude and lack of education, amplified by absence of reassurance.
Attitude: Too many practitioners believe that performance should always have unquestioned priority over correctness, reliability and safety.
Lack of education: Too many practitioners believe that constructs they should use are bad for performance even when this is not actually the case. They also do not seem to realise that premature optimisation often leads to worse performance.
Absence of reassurance: When languages and compilers do not provide the right kind of performance guarantees, practitioners with the above mentioned attitude and lack of education are most likely to engage in premature optimisation.
It seems to me that the latter represents opportunity for improvement.
I want to give two examples for performance guarantees by languages and compilers:
(1) tail call optimisation in functional languages
Practitioners of functional languages have no second thoughts about using recursion. They trust their tools to eliminate tail calls, and even generate iterative output for recursive input when it makes sense. They are far less likely to engage in premature optimisation.
Most functional languages provide a performance guarantee by making tail call optimisation mandatory.
(2) inline functions versus macros in GNU C
The documentation of the GNU C compiler has an entire chapter telling practitioners that function macros are evil and should be avoided, that GNU C's inline functions are better readable and just as efficient, yet, the culture of using macros persists.
Part of that may have to do with the wish for portability. The C standard does not make function inlining mandatory, it is only a suggestion to the compiler, not a mandate. Thus when using inline functions in place of function macros, the performance guarantee only applies to GNU C and the code would need to be edited when using another compiler because the inline attribute has a parameter: __inline__(always).
If C as a language would give an equivalent performance guarantee combined with education this would probably make a significant difference. A performance guarantee by the GNU C compiler alone, even with RMS speaking out against function macros, does not seem to be sufficient to change the culture.
Of course there is a good reason why the inline attribute is only a suggestion and not a mandate: Except for very trivial cases, the compiler always knows better when inlining makes sense and when it does not. If there was a mandate to inline across the board, it is very likely that a culture of "always inline everything" would develop.
IOW, lack of confidence in the compiler is a factor here: I better use a macro, just in case the compiler won't inline it. If the experience with functional languages and TCO is any indication, then this confidence can be developed.
Notably, the performance guarantee in function languages to eliminate tail call recursion is a smart guarantee. It doesn't say the compiler will eliminate all recursion, but only in a very specific situation: in tail call position.
IOW, performance guarantees will have to be a little smarter than just on versus off, or maybe versus always.
Simple scenarios could be given where inlining can be guaranteed.
One such scenario is a function that returns the value of a hidden variable.
Modula-2 practitioners do not need to be told that using hidden variables by placing their declarations into an implementation part and providing a function in the definition part to return its value is proper and safe design. I trust that most Modulans will actually stick to doing so in practise as well.
However, it is mind boggling how many computer science graduates out there do not understand the concept of data encapsulation aka information hiding. They know the terminology and concept, but they don't apply it consistently. There are just too many cases where people grant themselves exceptions when they might not and will not need to encapsulate or bypass encapsulation.
Data encapsulation is like being pregnant though. There is no such thing as "a little pregnant". One either uses encapsulation or one doesn't. I have asked a great many folks why they bypass encapsulation in particular code examples. The reasons given were almost always concern for performance, in some cases laziness.
To tackle laziness is very easy. Wirth had already done that in PIM where he recommended that variable declarations should either not be permitted within definition parts at all, or if they are permitted, then the variables should only be exported read-only.
Unfortunately though, he didn't implement his own recommendation in his own compiler and to my knowledge no other compiler ever followed that recommendation either. In M2R10 we have made this recommendation mandatory.
To tackle concern for performance is also fairly straightforward. An inline guarantee for variable accessor functions could be made in the language. Thus a function of the form ...
PROCEDURE foo ( ) : Foo <*INLINE*>;
RETURN foo (* hidden variable *)
... should always be inlined, the language specification should make it mandatory.
The same guarantee could be given for mutator procedures. A procedure of the form ...
PROCEDURE setFoo ( value : Foo ) <*INLINE*>;
foo := value
... should always be inlined, the language specification should make this mandatory, too.
There are some other common simple scenarios where inlining should be guaranteed. Outside of such guaranteed scenarios, inlining must remain a suggestion to the compiler, but for the select scenarios a suggestion is not good enough to change the habits of practitioners.
Smart performance guarantees of this kind can go a long way to improve both performance and reliability. Practitioners who stick to proper engineering practise would no longer be penalised in circumstances where doing the right thing will indeed lead to lesser performance and those who will sacrifice all else to performance no longer have reason nor motivation to engage in premature performance.
In M2R10 we made tail call optimisation mandatory, we made Wirth's recommendation of exporting global variables always read-only mandatory and we made inlining for getter functions and setter procedures mandatory when requested.
I am sure there are other simple optimisations of this kind that if guaranteed by the language specification will give practitioners the confidence to do the right thing and abstain from premature optimisation.