Faster C++: Move Construction and Perfect Forwarding Pete Isensee Advanced Technology Group Microsoft 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! Example Shallow struct Particle { Vector3 mPos; Vector3 mVel; Color mCol; }; Deep struct Texture { unsigned long mSize; unsigned long* mpBits; }; Deeper struct ParticleSystem { std::vector< Particle > mPar; Texture mTex; }; ParticleSystem particleSys(...); particleSys = StartExplosion(); particleSys += AddSmoke(); // Explosion begins // More particles added ParticleSystem particleSys(...); particleSys = StartExplosion(); particleSys += AddSmoke(); // Explosion begins // More particles added ParticleSystem particleSys(...); particleSys = StartExplosion(); particleSys += AddSmoke(); particleSys // Explosion begins // More particles added StartExplosion() v v t t … … v v t t … … v v t t … … v v t … t … Copying Temp Objects is Expensive 1000000 Particles 1 10 100 1,000 10,000 100,000 1,000,000 100000 10000 Ticks 1000 Perf of operator=(const ParticleSystem&) 100 10 1 1 10 100 1000 Particles 10000 100000 1000000 Copy (ticks) 6,113 7,201 5,543 8,579 56,614 635,962 6,220,013 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 a = b + c + d; // c+d is a temporary object // b+(c+d) is another temporary object What We Would Like… • Is a world where… – We could avoid unnecessary copies – In cases where it was safe to do so – Completely under programmer control • For example… ParticleSystem particleSys(...); particleSys = StartExplosion(); particleSys += AddSmoke(); particleSys // Explosion begins // More particles added StartExplosion() v v t t … … v v t t … … v v t t … v v t t Consider Assignment struct ParticleSystem { std::vector< Particle > mPar; Texture mTex; }; Canonical copy assignment ParticleSystem& operator=( const ParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Vector assignment (copy) mTex = rhs.mTex; // Texture assignment (copy) } return *this; } What we want ParticleSystem& operator=( <Magic Type> rhs ) { // move semantics here ... return *this; } 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 Example Copy assignment ParticleSystem& operator=( const ParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Particle vector copy mTex = rhs.mTex; // Texture copy } return *this; } Move assignment ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move mTex = std::move( rhs.mTex ); // Texture move } return *this; } 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 yes no Has a name yes no Moveable no* yes 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 4321 // rvalue std::string( “abc” ) // rvalue 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 Binding foo( foo( foo( foo( ParticleSystem&& ); const ParticleSystem&& ); ParticleSystem& ); const ParticleSystem& ); // // // // A: B: C: D: rvalue const rvalue lvalue const lvalue ParticleSystem particleSys; const ParticleSystem cparticleSys; foo( particleSys ); foo( StartExplosion() ); foo( cparticleSys ); // lvalue // rvalue // const lvalue Binding and Overload Resolution Rules Expression Reference Type rvalue T&& yes const T&& yes const rvalue const lvalue Priority highest yes T& const T& lvalue yes yes yes yes yes lowest std::move 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 ); } 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 ) { // Standard assignment operator Texture& Texture::operator=( ) { DestroyRange( mpFirst, mpLast ); //Texture&& call all rhs dtors Texture& Texture::operator=( const Texture& rhs ) { if(!= this != &rhs ) { mpFirst if( mpFirst nullptr ) free( ); if( this != &rhs ) { if( mpBits != nullptr// ) free( mpBits ); mpFirst = rhs.mpFirst; eviscerate if( mpBits != nullptr) mpBits ); mpBits = rhs.mpBits; //free( eviscerate mpLast = rhs.mpLast; mSize == rhs.mSize; rhs.mSize; mSize mpEnd = rhs.mpEnd; // rhs now empty shell mpBits = malloc( mSize = nullptr; //); clear rhs rhs.mpFirst rhs.mpBits = rhs.mpLast = rhs.mpEnd = nullptr; memcpy( mpBits, rhs.mpBits, mSize ); } } } return *this; return *this; return *this; } } } 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 Performance Revisited 1000000 operator=(const ParticleSystem&) 100000 Particles 1 10 100 1,000 10,000 100,000 1,000,000 10000 Ticks 1000 operator=(ParticleSystem&&) 100 10 1 1 10 100 1000 Particles 10000 100000 1000000 Copy (ticks) 6,113 7,201 5,543 8,579 56,614 635,962 6,220,013 Move (ticks) 1019 1100 968 1200 865 993 1173 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 Texture::Texture( Texture&& rhs ) : mpLast ( rhs.mpLast ), mpBits( rhs.mpBits ), // eviscerate mpEnd ( rhs.mpEnd ) mSize( rhs.mSize ) { { // rhs now an empty shell // rhs now an empty shell rhs.mpFirst = rhs.mpLast = rhs.mpEnd = nullptr; rhs.mpBits = nullptr; } } 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... } Func Templates Plus rvalues to the Rescue Powerful new rule in C++11. Given: template< typename T > void f( T&& t ); // template function Template rvalue ref param binds to anything Expression Reference Type rvalue template T&& yes T&& yes const T&& yes const rvalue yes yes const lvalue Priority yes highest yes lowest yes T& const T& lvalue yes yes yes yes 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&& ); Perfect Forwarding 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 template< class T > inline T&& // typical std::forward implementation forward( typename identity<T>::type& t ) noexcept { return static_cast<T&&>( t ); } Perfect Constructors Typical multi-arg ctor; doesn’t handle rvalues ParticleSystem::ParticleSystem( const std::vector<Particle>& par, const Texture& texture ) : mPar( par ), mTex( texture ) { } Perfect constructor; handles everything you throw at it! template< typename V, typename T > ParticleSystem::ParticleSystem( V&& par, T&& texture ) : mPar( std::forward<V>( par ) ), mTex( std::forward<T>( texture ) ) { } Implicit Special Member Functions Function Implicitly generated when Default ctor no other ctor explicitly declared Copy ctor no move ctor or move assign explicitly declared Copy assign no move ctor or move assign explicitly declared 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 • Rule of Three: if you define any of the first three, define all • Rule of Two Moves: If you define either move, define both 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; }; C++11 • STL containers move enabled – Including std::string • STL algorithms move enabled template< typename T > swap( T& a, T& b ) { T tmp( std::move( a ) ); a = std::move( b ); b = std::move( tmp ); } – Including sort, partition, swap • You get immediate speed advantages simply by recompiling 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&& ); }; Recommended Idioms: Raw Pointers T( T&& rhs ) : ptr( rhs.ptr ) // eviscerate { rhs.ptr = nullptr; // rhs: safe state } 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 ctor Move assignment 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 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 Compilers and Move Support Feature Microsoft GCC Intel Clang rvalue references VS 2010 4.3 11.1 2.9 STL move semantics VS 2010 4.3 11.1 2.9 nullptr VS 2010 4.6 12.1 2.9 variadic templates 4.3 12.1 2.9 defaulted/deleted funcs 4.4 12.0 3.0 noexcept 4.6 Exhaustive list: http://wiki.apache.org/stdcxx/C++0xCompilerSupport 3.0 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 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 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 Thanks! • • • • • • • Contact me: pkisensee@msn.com Slides: http://www.tantalon.com/pete.htm Scott Meyers: http://www.aristeia.com Stephan Lavavej: http://blogs.msdn.com Dave Abrahams: http://cpp-next.com Thomas Becker: http://thbecker.net Marc Gregoire: http://www.nuonsoft.com • Let me know what kind of results you see when you move enable your code Additional Reference Material C++ Standard References • N1610 (v0.1) 2004 http://www.openstd.org/jtc1/sc22/wg21/docs/papers/2004/n1610.html • N2118 (v1.0) 2006 http://www.openstd.org/jtc1/sc22/wg21/docs/papers/2006/n2118.html • N2844 (v2.0) 2009 (VC10 impl) http://www.openstd.org/jtc1/sc22/wg21/docs/papers/2009/n2844.html • N3310 (sections 840, 847, 858) (v2.1) 2011 (VC11 impl) http://www.openstd.org/jtc1/sc22/wg21/docs/cwg_defects.html • N3053 (v3.0) 2010 http://www.openstd.org/jtc1/sc22/wg21/docs/papers/2010/n3053.html rvalue References • • • • • • • • • • Scott Meyers’ Move Semantics and rvalue References: http://www.aristeia.com/TalkNotes/ACCU2011_MoveSemantics.pdf and http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvaluereferences Scott Meyers’ Adventures in Perfect Forwarding (C++ and Beyond 2011) Thomas Becker’s rvalue References Explained: http://thbecker.net/articles/rvalue_references/section_01.html STL’s blog: http://blogs.msdn.com/b/vcblog/archive/2009/02/03/rvalue-references-c-0xfeatures-in-vc10-part-2.aspx?PageIndex=3 Marc Gregoire’s Blog http://www.nuonsoft.com/blog/2009/06/07/the-move-constructor-invisual-c-2010/ C++11 Features in Visual Studio C++ 11 http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx Mikael Kilpelainen’s Lvalues and Rvalues http://accu.org/index.php/journals/227 Moving from lvalues http://cpp-next.com/archive/2009/09/move-it-with-rvalue-references Binary operators http://cpp-next.com/archive/2009/09/making-your-next-move/ Emplacement http://stackoverflow.com/questions/4303513/push-back-vs-emplace-back