I love to use method chaining to completely initialize objects and then store them in const variables. When analyzing the resulting code it turns out that this means the execution of many copy constructors. Therefore I have wondered whether C++ 11 move semantics might help optimizing method chaining.
Indeed I have been able to significantly speed up my code by adding overloads with ref qualifiers to my chain methods. Please consider this source code:
#include <chrono>
#include <iostream>
#include <string>
#undef DEBUGGING_OUTPUT
#undef ENABLE_MOVING
class Entity
{
public:
Entity() :
data(0.0), text("Standard Text")
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Constructing entity." << std::endl;
#endif
}
Entity(const Entity& entity) :
data(entity.data), text(entity.text)
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Copying entity." << std::endl;
#endif
}
Entity(Entity&& entity) :
data(entity.data), text(std::move(entity.text))
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Moving entity." << std::endl;
#endif
}
~Entity()
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Cleaning up entity." << std::endl;
#endif
}
double getData() const
{
return data;
}
const std::string& getText() const
{
return text;
}
void modify1()
{
data += 1.0;
text += " 1";
}
Entity getModified1() const &
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Lvalue version of getModified1" << std::endl;
#endif
Entity newEntity = *this;
newEntity.modify1();
return newEntity;
}
#ifdef ENABLE_MOVING
Entity getModified1() &&
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Rvalue version of getModified1" << std::endl;
#endif
modify1();
return std::move(*this);
}
#endif
void modify2()
{
data += 2.0;
text += " 2";
}
Entity getModified2() const &
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Lvalue version of getModified2" << std::endl;
#endif
Entity newEntity = *this;
newEntity.modify2();
return newEntity;
}
#ifdef ENABLE_MOVING
Entity getModified2() &&
{
#ifdef DEBUGGING_OUTPUT
std::cout << "Rvalue version of getModified2" << std::endl;
#endif
modify2();
return std::move(*this);
}
#endif
private:
double data;
std::string text;
};
int main()
{
const int interationCount = 1000;
{
const std::chrono::steady_clock::time_point startTimePoint =
std::chrono::steady_clock::now();
for (int i = 0; i < interationCount; ++i)
{
const Entity entity = Entity().getModified1().getModified1().getModified2().getModified2();
#ifdef DEBUGGING_OUTPUT
std::cout << "Entity has text " << entity.getText() << " and data "
<< entity.getData() << std::endl;
#endif
}
const std::chrono::steady_clock::time_point stopTimePoint =
std::chrono::steady_clock::now();
const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
std::chrono::duration<double>>(stopTimePoint - startTimePoint);
std::cout << "Method chaining has taken " << timeSpan.count() << " seconds."
<< std::endl;
}
{
const std::chrono::steady_clock::time_point startTimePoint =
std::chrono::steady_clock::now();
for (int i = 0; i < interationCount; ++i)
{
Entity entity;
entity.modify1();
entity.modify1();
entity.modify2();
entity.modify2();
#ifdef DEBUGGING_OUTPUT
std::cout << "Entity has text " << entity.getText() << " and data "
<< entity.getData() << std::endl;
#endif
}
const std::chrono::steady_clock::time_point stopTimePoint =
std::chrono::steady_clock::now();
const std::chrono::duration<double> timeSpan = std::chrono::duration_cast<
std::chrono::duration<double>>(stopTimePoint - startTimePoint);
std::cout << "Modification without method chaining has taken "
<< timeSpan.count() << " seconds." << std::endl;
}
return 0;
}
The version without method chaining is approximately 10 times faster here than the other one. As soon as I replace
#undef ENABLE_MOVING
by
#define ENABLE_MOVING
the version without method chaining remains only 1.5 times faster than the other one. So this is a great improvement.
Still I wonder whether I could optimize the code even more. When I switch to
#define DEBUGGING_OUTPUT
then I can see that there are new entities created for every call to getModified1() or getModified2(). The only advantage of move construction is that creation is cheaper. Is there a way to even prevent move construction and work on the original entity with method chaining?
Aucun commentaire:
Enregistrer un commentaire