Presentation is loading. Please wait.

Presentation is loading. Please wait.

Faster C++: Move Construction and Perfect Forwarding

Similar presentations


Presentation on theme: "Faster C++: Move Construction and Perfect Forwarding"— Presentation transcript:

1 Faster C++: Move Construction and Perfect Forwarding
In 2002, Howard Hinnant and some others proposed adding move semantics to C++ Back in 2003, Alexander Alexandrescu wrote on article in Dr. Dobbs about move constructors I first heard about move constructors in 2007 when Howard Hinnant presented the concept at BoostCon Pete Isensee Advanced Technology Group Microsoft

2 Problem Statement Copying deep objects is expensive
C++ is built on copy semantics STL containers store by value Compiler temporaries are copied by value Copying is often non-obvious in source code Games copy objects – a lot! Deep: objects that refer to additional memory Classic examples: strings, vectors, linked lists C++ philosophically favors copy Return Value Optimization helps, but doesn’t solve the problem

3 Example Deep Shallow Deeper struct Texture { unsigned long mSize;
unsigned long* mpBits; }; Deep Shallow struct Particle { Vector3 mPos; Vector3 mVel; Color mCol; }; struct ParticleSystem { std::vector< Particle > mPar; Texture mTex; }; Deeper Shallow: all PODs Deep: points to other memory Does this make you uncomfortable? Code perfectly reasonable Readable, correct, functional, w/ strict value semantics ParticleSystem particleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added

4 ParticleSystem particleSys(...);
Deep: objects that refer to additional memory Classic examples: strings, vectors, linked lists C++ philosophically favors copy Return Value Optimization helps, but doesn’t solve the problem ParticleSystem particleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added

5 particleSys StartExplosion() ParticleSystem particleSys(...);
particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added particleSys StartExplosion() t v t v t v t v t v t v StartExplosion: unnamed temporary object Assume particleSys is not empty and # particles is less than the explosion * That little equal sign killed us from a performance standpoint AddSmoke: vector reallocation, and likely some copying of texture memory t v t v

6 Copying Temp Objects is Expensive
Particles Copy (ticks) 1 6,113 10 7,201 100 5,543 1,000 8,579 10,000 56,614 100,000 635,962 1,000,000 6,220,013 Perf of operator=(const ParticleSystem&) Measured on Xbox 360 You can see the benefits of cache coherancy at 100 particles Logarithmic scale; linear shoots straight through the roof * With a small number of particles, the cost is primarily the allocation cost * With large number, the cost is copying memory Clearly, shouldn’t be using this type of semantic – you should just avoid temporaries

7 Avoiding Temporaries is Difficult
bool Connect( const std::string& server, ... ); if( Connect( “microsoft.com” ) ) // temporary object created v.push_back( X(...) ); // another temporary a = b + c; // b + c is a temporary object x++; // returns a temporary object Ref counting, but then you have to deal with synchronization and ref count locking Ref counting also breaks down (for becomes costly) for generic code, when you want to also handle lightweight types like int So what are we going to do? a = b + c + d; // c+d is a temporary object // b+(c+d) is another temporary object

8 What We Would Like… Is a world where… For example…
We could avoid unnecessary copies In cases where it was safe to do so Completely under programmer control For example… Imagine a world where… Sounds like a world of unicorns and butterflies and rainbows

9 particleSys StartExplosion() ParticleSystem particleSys(...);
particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added particleSys StartExplosion() t v t v t v t v t v t v TBD remove as we go down the list? StartExplosion: unnamed temporary object We’ve avoided two extra memory allocations We’ve avoided extra memory overhead (two copies of vectors, of textures) We’ve avoided one series of destructors We’ve avoided copying memory We’ve gone from an O(N) operation to constant time It’s time to look at some code… What does that second line of code expand too? t v t v

10 Consider Assignment Canonical copy assignment What we want
struct ParticleSystem { std::vector< Particle > mPar; Texture mTex; }; ParticleSystem& operator=( const ParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Vector assignment (copy) mTex = rhs.mTex; // Texture assignment (copy) } return *this; Canonical copy assignment ParticleSystem& operator=( <Magic Type> rhs ) { // move semantics here ... return *this; } What we want We want the compiler to select move behavior at compile time for move-safe objects We don’t care about rhs after the function is complete, because we moved from it

11 Solution: C++11 Standard to the Rescue
Don’t copy when you don’t need to; move instead Critically important for deep objects Key new language feature: rvalue references Enables move semantics, including Move construction Move assignment Perfect forwarding

12 Example Copy assignment Move assignment
ParticleSystem& operator=( const ParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Particle vector copy mTex = rhs.mTex; // Texture copy } return *this; Copy assignment ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move mTex = std::move( rhs.mTex ); // Texture move } return *this; Move assignment No const New notation New function

13 Movable Objects: rvalues
Move from = eviscerate thyself Every expression is either an lvalue or rvalue Always safe to move from an rvalue lvalue rvalue In memory yes no Can take its address Has a name Moveable no* It’s time to talk about rvalues Evisceration = resource stealing/pilfering Scott Meyers: “rip its guts out” Not types, expressions Historical from C: L=left, R=right L=locator = located in memory rvalues are expressions referring to anonymous temporary objects. Because they are temporary we can modify them without anybody noticing. Hardly ever safe to move from an lvalue

14 lvalue/rvalue examples
int a; // a is an lvalue ++x; // lvalue X x; // x is an lvalue x++; // rvalue X(); // X() is an rvalue *ptr // lvalue int a = 1+2; // a is an lvalue; 1+2 is an rvalue foo( x ); // x is an lvalue x+42 // rvalue foo( bar() ); // bar() is an rvalue “abc” // lvalue ++x vs. x++ 4321 // rvalue std::string( “abc” ) // rvalue

15 T&& rvalue References T&: reference (pre C++11)
T&: lvalue reference in C++11 T&&: rvalue reference; new in C++11 rvalue references indicate objects that can be safely moved from rvalue references bind to rvalue expressions lvalue references bind to lvalue expressions && does not equal reference to a reference

16 Binding foo( ParticleSystem&& ); // A: rvalue
foo( const ParticleSystem&& ); // B: const rvalue foo( ParticleSystem& ); // C: lvalue foo( const ParticleSystem& ); // D: const lvalue ParticleSystem particleSys; const ParticleSystem cparticleSys; foo( particleSys ); // lvalue foo( StartExplosion() ); // rvalue foo( cparticleSys ); // const lvalue

17 Binding and Overload Resolution Rules
Expression Reference Type rvalue const rvalue lvalue const lvalue Priority T&& yes highest const T&& T& const T& lowest const T& binds to everything – that’s why we use it a lot T&& binds only to non-const rvalues – those are objects we typically move from Two simple rules: obey const correctness, prevent rvalues from binding w/ modifiable lvalue references

18 std::move std::move ~= static_cast< T&& >(t)
ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move assignment mTex = std::move( rhs.mTex ); // Texture move assignment } return *this; std::move ~= static_cast< T&& >(t) Tells compiler: treat this named variable as an rvalue Highly complex implementation due to reference collapsing, parameter deduction and other arcane language rules template< class T > inline typename std::remove_reference<T>::type&& move( T&& t ) noexcept { using ReturnType = typename std::remove_reference<T>::type&&; return static_cast< ReturnType >( t ); } rhs is an lvalue! Generates absolutely zero code Has a great name

19 Move assign ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move assignment mTex = std::move( rhs.mTex ); // Texture move assignment } return *this; std::vector<T>& operator=( std::vector<T>&& rhs ) { if( this != &rhs ) { DestroyRange( mpFirst, mpLast ); // call all dtors if( mpFirst != nullptr ) free( mpFirst ); mpFirst = rhs.mpFirst; // eviscerate mpLast = rhs.mpLast; mpEnd = rhs.mpEnd; // rhs now empty shell rhs.mpFirst = rhs.mpLast = rhs.mpEnd = nullptr; } return *this; Texture& Texture::operator=( Texture&& rhs ) { if( this != &rhs ) { if( mpBits != nullptr ) free( mpBits ); mpBits = rhs.mpBits; // eviscerate mSize = rhs.mSize; rhs.mpBits = nullptr; // clear rhs } return *this; // Standard assignment operator Texture& Texture::operator=( const Texture& rhs ) { if( this != &rhs ) { if( mpBits != nullptr) free( mpBits ); mSize = rhs.mSize; mpBits = malloc( mSize ); memcpy( mpBits, rhs.mpBits, mSize ); } return *this; we can safely eviscerate rhs – it’s a temporary object that’s about to go away rhs: notice why its important that rhs be non-const normally taboo – this is critical for moves rhs destructor will be called – needs to be in a safe sensible state Imagine that the texture library was a 3P library – out of our control – not move enabled

20 Intermission Use rvalue reference semantics to enable moves
Use non-const rvalue: rhs is reset std::move tells compiler “this is really an rvalue” Binding rules allow gradual conversion Implement rvalue reference semantics as you go Start in low-level libraries Or start in high-level code, your choice

21 Performance Revisited
operator=(const ParticleSystem&) Particles Copy (ticks) Move (ticks) 1 6,113 1019 10 7,201 1100 100 5,543 968 1,000 8,579 1200 10,000 56,614 865 100,000 635,962 993 1,000,000 6,220,013 1173 operator=(ParticleSystem&&) Measured the cost of operator=(&) compared to operator=(&&) Always faster But wait, we haven’t even talked about move construction or perfect forwarding

22 Move Constructors ParticleSystem::ParticleSystem( ParticleSystem&& rhs ) : // invoke member move ctors mPar( std::move( rhs.mPar ) ), mTex( std::move( rhs.mTex ) ) { } vector<T>::vector( vector<T>&& rhs ) : mpFirst( rhs.mpFirst ), // eviscerate mpLast ( rhs.mpLast ), mpEnd ( rhs.mpEnd ) { // rhs now an empty shell rhs.mpFirst = rhs.mpLast = rhs.mpEnd = nullptr; } Texture::Texture( Texture&& rhs ) : mpBits( rhs.mpBits ), // eviscerate mSize( rhs.mSize ) { // rhs now an empty shell rhs.mpBits = nullptr; }

23 Perfect Forwarding Problem
Suppose we have some setter functions void ParticleSystem::SetTexture( const Texture& texture ) { mTex = texture; // We’d like to move if tx is a temporary } void ParticleSystem::SetTexture( Texture&& texture ) { mTex = std::move( texture ); // Move } void ParticleSystem::Set( const A& a, const B& b ) { // Uh-oh, we need three new overloads... } Gets complicated if you have lots of setters, setters with multiple parameters, or setters with template parameters, some of which are moveable (rvals) and some which are not

24 Func Templates Plus rvalues to the Rescue
Powerful new rule in C++11. Given: Template rvalue ref param binds to anything template< typename T > void f( T&& t ); // template function Expression Reference Type rvalue const rvalue lvalue const lvalue Priority template T&& yes highest T&& const T&& T& const T& lowest Remember the binding rules (green/red table) – this is a new rule. Even better, the type is preserved. If t is an lvalue, T’s type is deduced as [const] T&, we instantiate the T& version of f If t is an rvalue, T’s type is deduced as [const] T&& Awesome. With a single function, we always get the right results. We simply need to pass arguments as template parameters. Called perfect forwarding.

25 Binding rvalue Reference Template Params
Examples template< typename T > void f( T&& t ); // template function int a; const int ca = 42; f( a ); // instantiates f( int& ); f( ca ); // instantiates f( const int& ); f( StartExplosion() ); // instantiates f( ParticleSystem&& );

26 Perfect Forwarding std::forward<T> equivalent to
template< typename T > void ParticleSystem::SetTexture( T&& texture ) { mTex = std::forward<T>( texture ); // invokes right overload } std::forward<T> equivalent to static_cast<[const] T&&>(t) when t is an rvalue static_cast<[const] T&>(t) when t is an lvalue Remember that SetTexture function texture is an lvalue std::forward preserves constness Enables you to copy lvalues and move rvalues Name states the intent Absolutely no code generated Now we’ve seen an example of a “perfect setter”, but that’s not all perf. for. is good for… template< class T > inline T&& // typical std::forward implementation forward( typename identity<T>::type& t ) noexcept { return static_cast<T&&>( t ); }

27 Perfect Constructors Typical multi-arg ctor; doesn’t handle rvalues
ParticleSystem::ParticleSystem( const std::vector<Particle>& par, const Texture& texture ) : mPar( par ), mTex( texture ) { } Typical multi-arg ctor; doesn’t handle rvalues template< typename V, typename T > ParticleSystem::ParticleSystem( V&& par, T&& texture ) : mPar( std::forward<V>( par ) ), mTex( std::forward<T>( texture ) ) { } Perfect constructor; handles everything you throw at it! Could add an overload to handle rvalues, but there’s an even better solution

28 Implicit Special Member Functions
Implicitly generated when Default ctor no other ctor explicitly declared Copy ctor no move ctor or move assign explicitly declared Copy assign Move ctor no copy ctor, move assign or dtor explicitly declared Move assign no copy ctor, copy assign or dtor explicitly declared Dtor no dtor explicitly declared Do move ctor/assign ever get generated automatically by compiler? Fair amount of controversy regarding implicit move functions Rule of Three: if you define any of the first three, define all Rule of Two Moves: If you define either move, define both

29 Be Explicit About Implicit Special Functions
struct ParticleSystem { std::vector< Particle > mPar; // Copyable/movable object Texture mTex; // Copyable/movable object // Ctors ParticleSystem() = delete; ParticleSystem( const ParticleSystem& ) = default; ParticleSystem( ParticleSystem&& ) = default; // Assign ParticleSystem& operator=( const ParticleSystem& ) = default; ParticleSystem& operator=( ParticleSystem&& ) = default; // Destruction ~ParticleSystem() = default; }; New notation in C++11 If you had a raw pointer in this class, all bets are off

30 C++11 STL containers move enabled STL algorithms move enabled
Including std::string STL algorithms move enabled Including sort, partition, swap You get immediate speed advantages simply by recompiling template< typename T > swap( T& a, T& b ) { T tmp( std::move( a ) ); a = std::move( b ); b = std::move( tmp ); } Remember that I said you couldn’t move from lvalues? Wicked fast, super cool I’ve found that rvalue references are more challenging than they look We’re so used to copy semantics that move semantics take a while to click

31 Recommended Idioms: Moveable Types
struct Deep { Deep( const Deep& ); // Copy ctor Deep( Deep&& ); // Move ctor template< typename A, typename B > Deep( A&&, B&& ); // Perfect forwarding ctor Deep& operator=( const Deep& ); // Copy assignment Deep& operator=( Deep&& ); // Move assignment ~Deep(); template< typename A > // Deep setters void SetA( A&& ); };

32 Recommended Idioms: Raw Pointers
T( T&& rhs ) : ptr( rhs.ptr ) // eviscerate { rhs.ptr = nullptr; // rhs: safe state } Move ctor T& operator=( T&& rhs ) { if( this != &rhs ) { if( ptr != nullptr ) free( ptr ); ptr = rhs.ptr; // eviscerate rhs.ptr = nullptr; // rhs: safe state } return *this; Move assignment shared_ptr<T> is move enabled

33 Recommended Idioms: Higher Level Objs
T( T&& rhs ) : base( std::move( rhs ) ), // base m ( std::move( rhs.m ) ) // members { } Move ctor T& operator=( T&& rhs ) { if( this != &rhs ) { m = std::move( rhs.m ); // eviscerate } return *this; Move assignment Don’t have to “clear” rhs; the individual move operations will handle that

34 Recommended Idioms: Perfect Forwarding
template< typename A, typename B > T( A&& a, B&& b ) : // binds to any 2 params ma( std::forward<A>( a ) ), mb( std::forward<B>( b ) ) { } Ctor template< typename A > void SetA( A&& a ) // binds to anything { ma = std::forward<A>( a ); } Setter

35 Compilers and Move Support
Feature Microsoft GCC Intel Clang rvalue references VS 2010 4.3 11.1 2.9 STL move semantics nullptr 4.6 12.1 variadic templates defaulted/deleted funcs 4.4 12.0 3.0 noexcept Exhaustive list:

36 High Level Takeaways By overloading on rvalue references, you can branch at compile time on the condition that x is moveable (a temporary object) or not You can implement the overloading gradually Benefits accrue to deep objects Performance improvements can be significant Now you know how to move-enable your game engine Significant: orders of magnitude better

37 Further Research: Topics I Didn’t Cover
xvalues, glvalues, prvalues Emplacement (e.g. “placement insertion”) Create element within container, w/ no moves/copies Uses perfect forwarding and variadic functions Other scenarios where moving lvalues is OK Moves and exceptions Perfect forwarding not always so perfect e.g. integral and pointer types; bitfields, too noexcept and implicit move Consider std::move for final use of deep locals

38 Best Practices Update to compilers that support rvalue references
Return by value is now reasonable – both readable and fast Add move ctor/assignment/setters to deep objects Move idiom: this = rhs pointers, rhs pointers = null Use non-const rvalue references When moving, satisfy moved-from obj invariants Avoid return by const T – prevents move semantics Be explicit about implicit special functions Step thru new move code to ensure correctness Invariants: valid empty state – same as default constructed

39 Thanks! Contact me: pkisensee@msn.com
Slides: Scott Meyers: Stephan Lavavej: Dave Abrahams: Thomas Becker: Marc Gregoire: Let me know what kind of results you see when you move enable your code We’ve come to the end of our crash course in move semantics and rvalue ref No books – yet Go forth and make your games faster

40 Additional Reference Material

41 C++ Standard References
N1610 (v0.1) 2004 N2118 (v1.0) 2006 N2844 (v2.0) 2009 (VC10 impl) N3310 (sections 840, 847, 858) (v2.1) 2011 (VC11 impl) N3053 (v3.0) 2010

42 rvalue References Scott Meyers’ Move Semantics and rvalue References: and Scott Meyers’ Adventures in Perfect Forwarding (C++ and Beyond 2011) Thomas Becker’s rvalue References Explained: STL’s blog: Marc Gregoire’s Blog C++11 Features in Visual Studio C++ 11 Mikael Kilpelainen’s Lvalues and Rvalues Moving from lvalues Binary operators Emplacement


Download ppt "Faster C++: Move Construction and Perfect Forwarding"

Similar presentations


Ads by Google