Topic: ARC++ and Operator Efficiency
Author: jimad@microsoft.com (Jim Adcock)
Date: Fri, 18 Mar 1994 18:44:12 GMT Raw View
Everything you suggest here is well within the existing capabilities
of world-class optimizers. The fact that C++ compilers today do not
have world-class optimizers speaks more the the needless complexity
and special cases already IN the language rather than to the need
to add even more needless complexity and special cases to the language.
Author: jones@jameson.arclch.com ((Ben Jones))
Date: Fri, 11 Mar 94 11:55:35 EST Raw View
This is the seventh in a series of articles which will explore ideas for
extending the C++ language. These extensions are implemented in an
experimental preprocessor called ARC++ which takes the extended C++
syntax and generates ANSI C++. Those who wish to try out the ideas
being presented here may obtain a copy of ARC++ for the PC, Mac,
Sparcstation, or Iris from the anonymous ftp on: "arcfos1.arclch.com".
Please go to the directory "/pub" and download the file "arc.READ_ME"
for instructions. If you are interested in the other articles in the
series, you may download the files "arc.TUTOR1", "arc.TUTOR2", etc.
OPERATOR EFFICIENCY
===================
Ben Jones
(c) 1994 ARSoftware Corporation
jones@jameson.arclch.com
Introduction
============
With ordinary mathematical expressions, C++ can optimize your code
very effectively. When you create your own types, things get somewhat
more inefficient. Part of the reason for this is that the very syntax
of operator overloading guarantees that temporary objects are being
created and destroyed in the course of executing an expression
involving overloaded operators.
ARC++ extends the C++ language to make it possible to optimize the
use of operators:
* Constructor Operators and Functions operate directly on the result.
* Static Operators add symetry to operator definitions.
* Combination Operators allow several operators to generate a single
function call or inline expansion.
Operator Inefficiencies in C++
==============================
C++ Functions (and operators) which return objects currently require a
temporary to be declared and returned:
struct Vector
{
double x,y,z;
Vector operator + (const Vector &a) const
{
Vector r; // Allocate temporary vector on stack
r.x = x + a.x;
r.y = y + a.y;
r.z = z + a.z;
return r; // Return it
}
};
The + function must allocate a temporary vector and then return it.
That implies a block transfer of data from the stack to the return
value which is pointed to by a hidden parameter. A second block
transfer occurs when the assignment is performed, although it is
possible for a compiler to recognize this situation and optimize it
by having the hidden parameter point to the object being assigned to:
Vector a,b,c;
c = a+b; // Return value copied twice
Another issue is merely aesthetic. An operator function looks a
little strange inside of a class definition because it has one less
operand than it would have if defined outside of the class. Now,
except for the =, ++, --, and arithmetic assignment operators, most
operators do not modify their operands.
There are some functions which must be defined entirely outside of the
class definition even though they involve strictly objects of that
class. For example:
Vector abs(Vector &a) // Return absolute value of vector
{
Vector r;
r.x = a.x < 0 ? -a.x : a.x;
r.y = a.y < 0 ? -a.y : a.y;
r.z = a.z < 0 ? -a.z : a.z;
return r;
}
Vector a,b;
a = abs(b);
Constructor Operators in ARC++
==============================
ARC++ simplifies things and improves efficiency by allowing
"constructor" operators and functions:
struct Vector
{
double x,y,z;
constructor operator + (const Vector &a,const Vector &b)
{
x = a.x + b.x;
y = a.y + b.y;
z = a.z + b.z;
}
constructor abs(const Vector &a)
{
x = a.x < 0 ? -a.x : a.x;
y = a.y < 0 ? -a.y : a.y;
z = a.z < 0 ? -a.z : a.z;
}
};
The keyword "constructor" in front of "operator" indicates that a result
object is being constructed. The function has the expected number of
arguments. No temporary is created and returned in the process. The
compiler could also make the construction work directly on the result
of an assignment, if necessary.
The scoping rule for finding constructor operators and functions is
simply that if any operand refers to an object of a particular class,
check for a constructor operator or function in that class.
Static Operators
================
ARC++ also provides a "static" operator for dealing with operators which
do not modify the object and do not construct an object of this class.
Take for example, an operator for returning the dot product of a Vector.
In C++, this would be defined inside the class as follows:
struct Vector
{
double x,y,z;
double operator * (const Vector &a) const
{
return x*a.x + y*a.y + z*a.z;
}
};
In ARC++ this would be:
struct Vector
{
double x,y,z;
static double operator * (const Vector &a,const Vector &b)
{
return a.x*b.x + a.y*b.y + a.z*b.z;
}
};
The same scoping rule which allows a constructor operator to be found
also allows a static operator to be found.
Combination Operators
=====================
It turns out that even when the result of a constructor function is
assigned to an object, most C++ compilers are not smart enough to make
the constructor operate directly on that object unless it occurs in an
initialized declaration. The constructor operator does save one
unnecessary copy. A smarter compiler could avoid a second.
One other problem with operator overloading is that it
compartmentalizes the operations involved. Compilers for
supercomputers can often vectorize certain combinations of arithmetic
operations, chaining a multiply and an add together in a single
pipelined instruction. Operator overloading interferes with the
ability to see these operations side by side:
Matrix & operator + (const Matrix &,const Matrix &);
Matrix & operator * (const Matrix &,const Matrix &);
Matrix & operator = (Matrix &,const Matrix &);
Matrix a,b,c,d;
a = b*c + d;
This above expression translates to:
operator =(a,operator +(operator*(b,c),d));
Scientists and engineers like the ability to represent their computer
algorithms in a form very similar to the mathematical formulas they
use. If they do this, however, they pay for it in having very
inefficient code generated.
Combination operators in ARC++ provide the ability to selectively
optimize the usage of operators by causing a single function call or
inline expansion to occur as a result of the right set of operators
being used. In the above example, the following combination operator
can be defined:
Matrix & operator "?=?*?+?"
(Matrix&,const Matrix&,const Matrix&,const Matrix&);
a = b*c + d;
This would translate to:
operator "?=?*?+?" (a,b,c,d);
A combination operator is indicated by a string constant being used in
place of an operator in the declaration. Within the string, question
marks indicate the positions of operands, one for each actual
argument.
The combination operator is recognized within the ARC++ parse tree.
Parentheses are used to indicate the precedence which would have been
forced by parentheses in an expression. Unary and postfix operators
can be easily intermixed. If parentheses are used in the actual
expression unnecessarily, they do not make any difference in the
ability of ARC++ to recognize the combination:
Matrix operator "?+?*?" (const Matrix&,const Matrix&,const Matrix&);
a+b*c or a+(b*c)
Matrix operator "(?+?)*?" (const Matrix&,const Matrix&,const Matrix&);
(a+b)*c
Matrix& operator "?= -?" (Matrix&,const Matrix&);
a = -b;
Matrix& operator "?=?++" (Matrix&,Matrix&);
a = b++;
Additional Benefits of Combination Operators
============================================
One side benefit of combination operators is that it is now possible
to easily handle cases which would be quite complicated or even
impossible to represent in C++.
Multi-dimensional arrays can be handled by a single function rather
than by a complicated set of classes:
int & operator "?[?][?]" (Array&,int,int);
Array a;
a[10][20] = 100;
The mathematician's "a<b<c" construct is easily handled:
int operator "?<?<?" (int a,int b,int c)
{ return a < b && b < c; }
int a,b,c;
if (a < b < c) ...
Conclusions
===========
* Constructor and Static operators and functions improve C++
efficiency.
* They also support the idea of Exportable Classes (described in a
previous article) by making it possible to define operators and
functions completely within the class definition.
* Combination Operators should allow the code generated by ARC++ to
be optimized easily by a parallelizing C++ compiler.
* Combination Operators make formerly complicated constructs easy
to represent.