Reputation: 157
I've got some light objects to push around and manipulate, which I then like to include in a more complex one. There's a lookup table which should remain unmodified. The idea appears simple enough, but the one line doing this - b += c(a);
- creates an expensive temporary.
#include <vector>
static int count;
struct costly {
/* std::map<std::string, int> and whatnot */
int b;
~costly() { count++; }
costly(int b): b(b) { }
costly &operator+= (costly &rhs) { b += rhs.b; return *this; }
};
/* Note the assumption above constructor exists for rhs */
costly operator* (const costly &lhs, costly rhs) {
rhs.b *= lhs.b; return rhs;
}
struct cheap {
/* Consider these private or generally unaccessible to 'costly' */
int index, mul;
cheap(int index, int mul): index(index), mul(mul) { }
costly operator() (const std::vector<costly> &rhs) {
/* Can we do without this? */
costly tmp = rhs[index] * mul; return tmp;
}
};
int main(int argc, char* argv[]) {
std::vector<costly> a = {1, 2}; costly b(1); cheap c = {1, 2};
/* Above init also calls the destructor, don't care for now */
count = 0;
b += c(a);
return count;
}
I've been reading up on RVO and C++11's rvalues, but can't really wrap my head around them well enough yet to completely eliminate the introduced intermediate. And above only creates one because rhs's constructor is available. Initially I had this;
costly operator* (costly lhs, int rhs) {
lhs.b *= rhs; return lhs;
}
/* ... */
costly operator() (const std::vector<costly> &rhs) {
return rhs[index] * mul;
}
But that, counter to my intuition, resulted in count
even being 2. Why's the compiler not getting my intentions?
Upvotes: 2
Views: 116
Reputation: 1820
This is a completely different approach, that's orthogonal to whether you optimize via RVO, but here goes:
Since your the inner data-member that makes the copy expensive is more or less const, why not just avoid copying that particular member?
If you change costly
like so:
struct costly {
shared_ptr<const map<string, int>> lookup_table;
int m;
...
};
Copying becomes much cheaper. Note the pointer to the table is non-const even though the map it points to is const.
Sean Parent gave a really good talk about this, regarding how they implemented history and layers in Photoshop. I can't look for the URL as of now because my bandwidth is limited.
Upvotes: 1
Reputation: 15334
I think part of the problem is the arithmetic operators are best suited to value types that are relatively cheap to copy. If you want to avoid copying costly
at all I think it is best to avoid overloading these operators.
It may put too much logic on costly
but you could add a function to costly that does exactly what you want without making any copies:
void addWithMultiple(const costly& rhs, int mul) {
b += rhs.b * mul;
}
which can then by called by cheap
like so:
void operator() (costly &b, const std::vector<costly> &a) {
b.addWithMultiple(a[index], mul);
}
But it is a considerable refactoring from what you started with so might not meet all your needs.
Upvotes: 0
Reputation: 227518
RVO does not apply to function parameters, so your *
operators are inhibiting it. In order to enable RVO, you need a local copy of the parameter. You can then optimize by providing an overload taking an rvalue reference (provided costly
has an efficient move copy constrctor). For example,
costly operator*(const costly& c, int i)
{
costly ret = c;
ret += 1;
return ret;
}
costly operator*(costly&& c, int i)
{
costly ret = std::move(c);
ret += 1;
return ret;
}
Upvotes: 1