manpagez: man pages & more
info ginac
Home | html | info | man
[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

A.1 Expressions are reference counted

In GiNaC, there is an intrusive reference-counting mechanism at work where the counter belongs to the algebraic objects derived from class basic but is maintained by the smart pointer class ptr, of which ex contains an instance. If you understood that, you can safely skip the rest of this passage.

Expressions are extremely light-weight since internally they work like handles to the actual representation. They really hold nothing more than a pointer to some other object. What this means in practice is that whenever you create two ex and set the second equal to the first no copying process is involved. Instead, the copying takes place as soon as you try to change the second. Consider the simple sequence of code:

 
#include <iostream>
#include <ginac/ginac.h>
using namespace std;
using namespace GiNaC;

int main()
{
    symbol x("x"), y("y"), z("z");
    ex e1, e2;

    e1 = sin(x + 2*y) + 3*z + 41;
    e2 = e1;                // e2 points to same object as e1
    cout << e2 << endl;     // prints sin(x+2*y)+3*z+41
    e2 += 1;                // e2 is copied into a new object
    cout << e2 << endl;     // prints sin(x+2*y)+3*z+42
}

The line e2 = e1; creates a second expression pointing to the object held already by e1. The time involved for this operation is therefore constant, no matter how large e1 was. Actual copying, however, must take place in the line e2 += 1; because e1 and e2 are not handles for the same object any more. This concept is called copy-on-write semantics. It increases performance considerably whenever one object occurs multiple times and represents a simple garbage collection scheme because when an ex runs out of scope its destructor checks whether other expressions handle the object it points to too and deletes the object from memory if that turns out not to be the case. A slightly less trivial example of differentiation using the chain-rule should make clear how powerful this can be:

 
{
    symbol x("x"), y("y");

    ex e1 = x + 3*y;
    ex e2 = pow(e1, 3);
    ex e3 = diff(sin(e2), x);   // first derivative of sin(e2) by x
    cout << e1 << endl          // prints x+3*y
         << e2 << endl          // prints (x+3*y)^3
         << e3 << endl;         // prints 3*(x+3*y)^2*cos((x+3*y)^3)
}

Here, e1 will actually be referenced three times while e2 will be referenced two times. When the power of an expression is built, that expression needs not be copied. Likewise, since the derivative of a power of an expression can be easily expressed in terms of that expression, no copying of e1 is involved when e3 is constructed. So, when e3 is constructed it will print as 3*(x+3*y)^2*cos((x+3*y)^3) but the argument of cos() only holds a reference to e2 and the factor in front is just 3*e1^2.

As a user of GiNaC, you cannot see this mechanism of copy-on-write semantics. When you insert an expression into a second expression, the result behaves exactly as if the contents of the first expression were inserted. But it may be useful to remember that this is not what happens. Knowing this will enable you to write much more efficient code. If you still have an uncertain feeling with copy-on-write semantics, we recommend you have a look at the C++-FAQ lite by Marshall Cline. Chapter 16 covers this issue and presents an implementation which is pretty close to the one in GiNaC.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]
© manpagez.com 2000-2024
Individual documents may contain additional copyright information.