1 / 42

Faster C++: Move Construction and Perfect Forwarding

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

ira-hooper
Download Presentation

Faster C++: Move Construction and Perfect Forwarding

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Faster C++:Move Construction and Perfect Forwarding 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!

  3. Example Deep struct Texture { unsigned long mSize; unsigned long* mpBits; }; Shallow Deeper struct Particle { Vector3 mPos; Vector3 mVel; Color mCol; }; structParticleSystem { std::vector< Particle > mPar; Texture mTex; }; ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added

  4. ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added

  5. ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added particleSys StartExplosion() v v v v v v … … … … … t … … t t t t t v t v t …

  6. Copying Temp Objects is Expensive Perfof operator=(constParticleSystem&)

  7. Avoiding Temporaries is Difficult bool Connect( conststd::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

  8. 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…

  9. ParticleSystemparticleSys(...); particleSys = StartExplosion(); // Explosion begins particleSys += AddSmoke(); // More particles added particleSys StartExplosion() v v v v … … … t … t t t v v t t … v v t t

  10. Consider Assignment structParticleSystem { std::vector< Particle > mPar; Texture mTex; }; Canonical copy assignment What we want ParticleSystem& operator=( constParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Vector assignment (copy) mTex= rhs.mTex; // Texture assignment (copy) } return *this; } ParticleSystem& operator=( <Magic Type>rhs ) { // move semantics here ... return *this; }

  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=( constParticleSystem& rhs ) { if( this != &rhs ) { mPar = rhs.mPar; // Particle vector copy mTex= rhs.mTex; // Texture copy } return *this; } ParticleSystem& operator=( ParticleSystem&& rhs ) { if( this != &rhs ) { mPar = std::move( rhs.mPar ); // Vector move mTex= std::move( rhs.mTex ); // Texture move } return *this; }

  13. Movable Objects: rvalues • Move from = eviscerate thyself • Every expression is either an lvalue or rvalue • Always safe to move from an rvalue

  14. lvalue/rvalue examples ++x; // lvalue int a; // a is an 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 “abc” // lvalue foo( bar() ); // bar() is an rvalue 4321 // rvalue std::string( “abc” ) // rvalue

  15. rvalue References T&& • 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

  16. Binding foo( ParticleSystem&& ); // A: rvalue foo( constParticleSystem&& ); // B: constrvalue foo( ParticleSystem& ); // C: lvalue foo( constParticleSystem& ); // D: constlvalue ParticleSystemparticleSys; constParticleSystemcparticleSys; foo( particleSys ); // lvalue foo( StartExplosion() ); // rvalue foo( cparticleSys ); // constlvalue

  17. Binding and Overload Resolution Rules

  18. 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 typenamestd::remove_reference<T>::type&& move( T&& t ) noexcept { using ReturnType = typenamestd::remove_reference<T>::type&&; return static_cast< ReturnType >( t ); }

  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; } // 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; } 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; }

  20. Intermission • Use rvalue reference semantics to enable moves • Use non-constrvalue: 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=(constParticleSystem&) operator=(ParticleSystem&&)

  22. Move Constructors 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; } ParticleSystem::ParticleSystem( ParticleSystem&& rhs ) : // invoke member move ctors mPar( std::move( rhs.mPar ) ), mTex( std::move( rhs.mTex ) ) { }

  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( constA& a, const B& b ) { // Uh-oh, we need three new overloads... }

  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

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

  26. 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 ); }

  27. Perfect Constructors Typical multi-argctor; doesn’t handle rvalues ParticleSystem::ParticleSystem( conststd::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 ) ) { }

  28. Implicit Special Member 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 structParticleSystem { std::vector< Particle > mPar; // Copyable/movable object Texture mTex; // Copyable/movable object // Ctors ParticleSystem() = delete; ParticleSystem( constParticleSystem& ) = default; ParticleSystem( ParticleSystem&& ) = default; // Assign ParticleSystem& operator=( constParticleSystem& ) = default; ParticleSystem& operator=( ParticleSystem&& ) = default; // Destruction ~ParticleSystem() = default; };

  30. C++11 template< typename T > swap( T& a, T& b ) { T tmp( std::move( a ) ); a = std::move( b ); b = std::move( tmp ); } • STL containers move enabled • Including std::string • STL algorithms move enabled • Including sort, partition, swap • You get immediate speed advantages simply by recompiling

  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

  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

  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 Exhaustive list: http://wiki.apache.org/stdcxx/C++0xCompilerSupport

  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

  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

  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-constrvalue 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

  39. 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

  40. Additional Reference Material

  41. C++ Standard References • N1610 (v0.1) 2004 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1610.html • N2118 (v1.0) 2006 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2118.html • N2844 (v2.0) 2009 (VC10 impl) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2844.html • N3310 (sections 840, 847, 858) (v2.1) 2011 (VC11 impl) http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html • N3053 (v3.0) 2010 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3053.html

  42. rvalueReferences • Scott Meyers’ Move Semantics and rvalueReferences: http://www.aristeia.com/TalkNotes/ACCU2011_MoveSemantics.pdf and http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references • Scott Meyers’ Adventures in Perfect Forwarding (C++ and Beyond 2011) • Thomas Becker’s rvalueReferences 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-0x-features-in-vc10-part-2.aspx?PageIndex=3 • Marc Gregoire’s Blog http://www.nuonsoft.com/blog/2009/06/07/the-move-constructor-in-visual-c-2010/ • C++11 Features in Visual Studio C++ 11 http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx • Mikael Kilpelainen’sLvalues and Rvalueshttp://accu.org/index.php/journals/227 • Moving from lvalueshttp://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

More Related