overhead of copying: doubles vs pointers - c++

the overhead of copying two doubles can be noticeable but often less
than what a pair of pointers impose
from C++ prog. lang.
could someone explain it?

This is taken from Chapter 11 of "The C++ Programming Language" by Stroustrup.
Let's look at the quote in context:
We defined the complex operators to take arguments of type complex. This implies that for each use of a complex operator, each operand is copied. The overhead of copying two doubles can be noticeable but often less than what a pair of pointers impose (access through a pointer can be relatively expensive). Unfortunately, not all classes have a conveniently small representation. To avoid excessive copying, one can declare functions to take reference arguments. For example:
class Matrix {
double m[4][4];
public:
Matrix();
friend Matrix operator+(const Matrix&, const Matrix&);
friend Matrix operator*(const Matrix&, const Matrix&);
};
All he is saying is that, since complex is small, it is acceptable to pass it around by value rather than by const reference. This, however, would not be acceptable for larger types, such as Matrix above. For such type, passing by const reference is the preferred method since it would avoid having to copy the whole matrix.

Related

`std::move` an Eigen object in a constructor?

The Eigen3 documentation warns against passing Eigen objects by value, but they only refer to objects being used as function arguments.
Suppose I'm using Eigen 3.4.0 and C++20. If I have a struct with an Eigen member, does this mean I can't std::move a pass-by-value in the constructor? Do I need to pass-by-reference and copy the object? Or is this handled somehow by modern move-semantics?
If I can't std::move Eigen objects in a constructor, does this mean I should explicitly delete the move-constructors from my struct?
For example,
#include <utility>
#include <Eigen/Core>
struct Node {
Eigen::Vector3d position;
double temperature;
// is this constructor safe to use?
Node(Eigen::Vector3d position_, const double temperature_)
: position(std::move(position_)), temperature(temperature_) {}
// or must it be this?
Node(const Eigen::Vector3d& position_, const double temperature_)
: position(position_), temperature(temperature_) {}
// also, should move-constructors be explicitly deleted?
Node(Node&&) = delete;
Node& operator=(Node&&) = delete;
};
There is nothing magic about Eigen objects. Fixed sized types such as Vector3d behave like std::array. Dynamic sized types like VectorXd behave like std::vector.
Pass-by-value for a dynamic sized type typically is a mistake because it usually invokes a copy construction which can be very expensive for large matrices. Pass-by-reference (const, lvalue, rvalue) is almost always the right choice [footnote 1].
Pass-by-value for fixed-sized types can be a benefit because the first few arguments are usually passed in registers (depending on the platform). This avoids spilling values to the stack. However, this doesn't work for Eigen. I assume they declare a destructor, even if they don't need one. That turns any pass-by-value into pass-by-reference to a hidden copy. You can see this in godbolt. This seems like a missed optimization in Eigen.
In conclusion: Use pass-by-reference. Move-construction makes sense for dynamic sized eigen arrays. It makes no difference for fixed sized types.
Footnote 1: A rare exception can be cases were you would need to do a copy anyway inside the function.

Is ampersand(&) important in this case?

What's the difference between
Complex operator+(Complex& A, Complex& B) {
double re=A.getReal()+B.getReal();
double im=A.getImg()+B.getImg();
Complex C(re, im);
return C;
}
and this(without &):
Complex operator+(Complex A, Complex B) {
double re=A.getReal()+B.getReal();
double im=A.getImg()+B.getImg();
Complex C(re, im);
return C;
}
Primarily, it is important to not use a reference to non-const for a function that doesn't modify the object through the reference. Using a reference to non-const will prevent the operator from being used with rvalue arguments.
Using a reference in this case may be important or it might not be. It is only relevant for optimisation purpose. If the function is not used in a hot part of the program, then its speed may not be important.
Assuming its speed is important, then the importance of the argument type depends on on many factors. For example if function is expanded inline then the choice probably doesn't matter at all. If it isn't inlined, then it can depend on the capabilities of the target system. On one system, the reference may be faster, on another system the value may be faster, while on others there may not be significant difference.
You can find out both which is faster, and whether it is significant to your program by measuring the different choices.
Note that if you do use a reference, then you should use a reference to const here.
In the first case the overload of the + operator receives as parameters a reference of A and a reference of B. This means that no copy constructor is called. Also, if you modify A (for example) by setting the real part to 0, you will see this modification at A’s real part after returning from the function.
In the second case, the overload of + operator received a copy of A and a copy of B. In this case the copy constructor is called. Any modification to A or B inside the function are not visible after the function ends.
Why is sometimes better to avoid the call of the copy constructor? It depends on the members of your class. Imagine that your class has a member that stores a vector with 1.000.000 elements. The copy constructor should allocate a one million elements vector and then copy its data. This operation takes time. So in this case is better to avoid the call of the copy constructor. But if the members of your class are simple double values, as in your example, you can use your second definition without problems.
Also, in the first case, if you don’t want to allow any modifications to A or B, you can use a const reference, like bellow:
Complex operator+(const Complex& A, const Complex& B);

Eigen non constant MatrixReplacement for sparse solver

I want to use matrix free sparse solvers with custom matrix-vector product object. Here is great example how to to it - https://eigen.tuxfamily.org/dox/group__MatrixfreeSolverExample.html
But in this example custom matrix-product object should be constant due to generic_product_impl signature
template<typename Dest>
static void scaleAndAddTo(
Dest& dst,
const MatrixReplacement& lhs,
const Rhs& rhs,
const Scalar& alpha)
In many my problems i need a lot of temporary buffers for each product call. It's pretty wise to allocate them once but i can't store them inside MatrixReplacement because it passed as const.
Is it possible in Eigen to overcome this problem?
There are two immediate options:
Use the mutable keyword for the members that need to change in const methods (i.e. your temporary buffers). This keyword makes sense where observable behavior of your class is const, despite you needing to modify members. Examples include cached values, mutexes, or your buffers.
C++ is not perfectly strict with propagating const. A const unique_ptr<T> will return a (non-const) T& when dereferenced (because the const says "you can't change the pointer", not "you can't change the pointee"; this is the same with builtin pointers). You can similarly wrap your "real" sparse matrix class in something that pretends to be const but allows non-const access to the matrix if the STL smart pointers are insufficient. If you give it an appropriate name then it's not as terrible as it sounds.
I recommend option 1.

In practice, would a custom Double class be much slower than the built-in double?

So suppose I do something like
class Double {
double m_double;
public:
Double() { }
Double(double d) : m_double(d) { }
operator double() const { return m_double; }
operator double&() { return m_double; }
};
Maybe I later want to extend this to make NaN a bit more friendly (by adding a bool say), etc.
My question is, do you think off the top of your head that this Double (and possible extensions on it) would be "measurably" slower than using the built-in double directly?
If you have some experience in working with large data sets, with vectors, copying/moving around vectors of such data, etc. - I hope you can give me some concrete insights/pointers/tips regarding this topic based on your experience.
Modern compilers do excellent optimizations. Single scalar en-wrapped in a class usually performs as well as plain one. If you add additional checking into member operators then it will cost just what you added. If you need to apply additional limitations to double then that is good idea.
Example: std::array<T,N> it is ... in essence just an array. I have failed to find test that demonstrates any overhead when comparing to raw array. The added limitations and container-like functionality make it valuable.
Avoid having operators that convert silently to scalars like operator double(). Compilers apply implicit conversions to typos with amazing ease and sometimes achieve that defects compile. Later it takes some time to realize why it is working like it is. Make the conversions more explicit like double raw() const. The resulting code is easier to understand and runs as fast.
Example: std::array<T,N> does not convert to raw pointer to first element like ordinary array. User has to use &a[0] to get raw pointer to first element. That makes it lot safer and easier to understand in code. It works as quickly (cost of the operation is 0 in optimized code).

using reference and pointer for operator overloading on large objects

In the book of “The C++ Programming Language”, the author gives the following example and several claims.
class Matrix {
double m[4][4];
public:
Matrix( );
friend Matrix operator+(const Matrix&, const Matrix&)
};
Matrix operator+(const Matrix& arg1, const Matrix& arg2)
{
Matrix sum;
for (int i=0; i<4; i++)
sum.m[i][j ]=arg1.m[i][j]+arg2.m[i][j];
return sum;
}
The book claims that
references allow the use of expressions involving the usual arithmetic operators for large objects without excessive copying. Pointers cannot be used because it is not possible to redefine the meaning of an operator applied to a pointer.
I do not understand what does “excessive copying” refer to in the above statement. And for the statement of “Pointers cannot be used because it is not possible to redefine the meaning of an operator applied to a pointer”, I am just totally lost. Thanks for the explanation.
If operator+ was instead declared as taking its operands by value, e.g.,
Matrix operator+(Matrix arg1, Matrix arg2)
then copies of arg1 and arg2 would have to be made to be passed into the function. A simple matrix addition, e.g.,
Matrix x, y;
x + y;
would require copies of both x and y to be made; effectively, you end up having to copy 32 doubles, which, while not extremely expensive in this case, is not particularly cheap either. When you take an argument by reference, no copy has to be made.
Note that some operator overloads must take their argument by reference. As a common example, consider the << stream insertion operator: it must take its std::istream operand by reference because it is impossible to copy the stream.
Pointers cannot be used because operators cannot be overloaded for pointers: at least one operand of each operator overload must be a class or enumeration type. Even if you could use pointers, the syntax would be very awkward; instead of using x + y as shown above, you would need to use &x + &y.
The "excessive copying" part is simple. If there were no & in the parameter list, the matrices would be copied when being passed in.
References are similar to C++ pointers in some ways, and they are almost always implemented using pointers internally. One of the differences, which the book notes, is that you can use operator overloading on references to an instance of a classs.
However, you can't do the below (or any equivalent syntax):
Matrix operator+(const Matrix *arg1, const Matrix *arg2)
So you can't write:
Matrix *a, *b, *c;
c = a + b;
The reason for not allowing user defined operators for built-in types, like pointers or ints, is of course that they already have operators defined for them by the language.