Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CBlockLocator: performance-move-const-arg Clang tidy fixup #25963

Merged
merged 1 commit into from
Aug 31, 2022

Conversation

jonatack
Copy link
Member

Fix Clang-tidy CI errors on master. See https://cirrus-ci.com/task/4806752200818688?logs=ci#L4696 for an example.

@jonatack jonatack marked this pull request as ready for review August 31, 2022 11:30
@jonatack
Copy link
Member Author

The Clang tidy CI passed, so opening this up for review.

@sipa
Copy link
Member

sipa commented Aug 31, 2022

The change on line 52 makes sense, but the one on 316 seems like a step backwards?

@jonatack jonatack force-pushed the performance-move-const-arg-fixups branch from af7a40c to f8e0d41 Compare August 31, 2022 12:02
@jonatack
Copy link
Member Author

The change on line 52 makes sense, but the one on 316 seems like a step backwards?

Hm, RVO wouldn't be affected by the move of an argument (rather than of the object itself)?

Repushed without that change to see if the tidy job remains appeased.

@sipa
Copy link
Member

sipa commented Aug 31, 2022

Hm, RVO wouldn't be affected by the move of an argument (rather than of the object itself)?

I don't see how RVO could apply here. It's an argument to a constructor. The fact that that constructor is invoked in a return statement doesn't matter.

@jonatack jonatack changed the title CBlockLocator: performance-move-const-arg Clang tidy fixups CBlockLocator: performance-move-const-arg Clang tidy fixup Aug 31, 2022
@jonatack
Copy link
Member Author

src/headerssync.cpp:316:26: error: passing result of std::move() as a const reference argument; no move will actually happen [performance-move-const-arg,-warnings-as-errors]
    return CBlockLocator{std::move(locator)};
                         ^~~~~~~~~~       ~

@jonatack jonatack force-pushed the performance-move-const-arg-fixups branch from f8e0d41 to 15fdd6a Compare August 31, 2022 12:30
@sipa
Copy link
Member

sipa commented Aug 31, 2022

@jonatack Oh! The issue is just that CBlockLocator has no constructor that accepts an rvalue uint256. The std::move there has no effect, but the correct solution is adding an rvalue-accepting constructor, not getting rid of the std::move.

@jonatack
Copy link
Member Author

@jonatack Oh! The issue is just that CBlockLocator has no constructor that accepts an rvalue uint256. The std::move there has no effect, but the correct solution is adding an rvalue-accepting constructor, not getting rid of the std::move.

Ah interesting! Learning.

@vasild
Copy link
Contributor

vasild commented Aug 31, 2022

ACK af7a40c
(the previous push with the two changes)

Both warnings are legit because no move will happen in either case.

But consider this, like @sipa suggested above, would avoid copying:

diff
diff --git i/src/chain.cpp w/src/chain.cpp
index 19c35b5012..66a0830394 100644
--- i/src/chain.cpp
+++ w/src/chain.cpp
@@ -46,13 +46,13 @@ std::vector<uint256> LocatorEntries(const CBlockIndex* index)
     }
     return have;
 }

 CBlockLocator GetLocator(const CBlockIndex* index)
 {
-    return CBlockLocator{std::move(LocatorEntries(index))};
+    return CBlockLocator{LocatorEntries(index)};
 }

 CBlockLocator CChain::GetLocator() const
 {
     return ::GetLocator(Tip());
 }
diff --git i/src/primitives/block.h w/src/primitives/block.h
index 76aba6c899..8c51d280b8 100644
--- i/src/primitives/block.h
+++ w/src/primitives/block.h
@@ -120,13 +120,13 @@ public:
 struct CBlockLocator
 {
     std::vector<uint256> vHave;

     CBlockLocator() {}

-    explicit CBlockLocator(const std::vector<uint256>& vHaveIn) : vHave(vHaveIn) {}
+    explicit CBlockLocator(std::vector<uint256>&& vHaveIn) : vHave(std::move(vHaveIn)) {}

     SERIALIZE_METHODS(CBlockLocator, obj)
     {
         int nVersion = s.GetVersion();
         if (!(s.GetType() & SER_GETHASH))
             READWRITE(nVersion);

@maflcko
Copy link
Member

maflcko commented Aug 31, 2022

Using && would force all call sites to use std::move. Thus, it makes it impossible to use on const&.

You can simply change the constructor to allow anything:

explicit CBlockLocator(std::vector<uint256> have) : vHave{std::move(have)} {}

@sipa
Copy link
Member

sipa commented Aug 31, 2022

@MarcoFalke I think all call sites already pass in rvalues. And an rvalue argument is still more efficient than a by-value one, as it avoids constructing the temporary vector entirely (not just avoiding allocating its storage buffer).

Co-authored-by: "Pieter Wuille <pieter@wuille.net>"
Co-authored-by: "Vasil Dimov <vd@FreeBSD.org>"
Co-authored-by: "MarcoFalke <falke.marco@gmail.com>"
@jonatack jonatack force-pushed the performance-move-const-arg-fixups branch from 15fdd6a to 6b24dfe Compare August 31, 2022 13:12
@maflcko
Copy link
Member

maflcko commented Aug 31, 2022

an rvalue argument is still more efficient than a by-value one

Correct. Personally, I think it is fine to have an overhead equivalent to constructing an empty vector. When there is a new call-site with a const reference in the future, the constructor will need to be changed anyway. Either a single one that accepts a pure value, or a new one that accepts a const& (alongside the && constructor), which is what the stdlib is usually doing.

But anything should be fine here.

review ACK 6b24dfe

@maflcko maflcko added this to the 24.0 milestone Aug 31, 2022
@vasild
Copy link
Contributor

vasild commented Aug 31, 2022

With a by-value argument there is construct+move+move, with an rval argument, there is construct+move:

rv.cc
#include <iostream>

/* The rule of five. */
class R5
{
public:
    R5(int x, double y)
    {
        x_ = x;
        y_ = y;
        std::cout << "R5::R5() this=" << this << " x=" << x_ << ", y=" << y_ << std::endl;
    }

    ~R5()
    {
        std::cout << "R5::~R5() this=" << this << " x=" << x_ << ", y=" << y_ << std::endl;
    }

    R5(const R5& other)
    {
        std::cout << "R5::R5(const R5&) this=" << this << " other=" << &other << " x=" << other.x_
                  << ", y=" << other.y_ << std::endl;
        x_ = other.x_;
        y_ = other.y_;
    }

    R5(R5&& other)
    {
        std::cout << "R5::R5(R5&&) this=" << this << " other=" << &other << " x=" << other.x_
                  << ", y=" << other.y_ << std::endl;
        x_ = other.x_;
        y_ = other.y_;
        other.x_ = 0;
        other.y_ = 0;
    }

    R5& operator=(const R5& other)
    {
        std::cout << "R5::operator=(const R5&) this=" << this << " x=" << x_ << ", y=" << y_
                  << " other=" << &other << " x=" << other.x_ << ", y=" << other.y_ << std::endl;
        x_ = other.x_;
        y_ = other.y_;
        return *this;
    }

    R5& operator=(R5&& other)
    {
        std::cout << "R5::operator=(R5&&) this=" << this << " x=" << x_ << ", y=" << y_
                  << " other=" << &other << " x=" << other.x_ << ", y=" << other.y_ << std::endl;
        x_ = other.x_;
        y_ = other.y_;
        other.x_ = 0;
        other.y_ = 0;
        return *this;
    }

    std::ostream& operator<<(std::ostream& os) { return os << "(x=" << x_ << ", y=" << y_ << ")"; }

    int x_;
    double y_;
};

class CBlockLocator
{
public:
#if 0
    explicit CBlockLocator(R5 r5) : m_r5{std::move(r5)} {}
#else
    explicit CBlockLocator(R5&& r5) : m_r5{std::move(r5)} {}
#endif

    R5 m_r5;
};

CBlockLocator f3()
{
    R5 r5{2, 3.4};
    return CBlockLocator{std::move(r5)};
}

int main(int, char**)
{
    f3();
    return 0;
}

output with R5 r5 arg:

R5::R5() this=0x7fffffffe5e8 x=2, y=3.4
R5::R5(R5&&) this=0x7fffffffe5d8 other=0x7fffffffe5e8 x=2, y=3.4
R5::R5(R5&&) this=0x7fffffffe610 other=0x7fffffffe5d8 x=2, y=3.4
R5::~R5() this=0x7fffffffe5d8 x=0, y=0
R5::~R5() this=0x7fffffffe5e8 x=0, y=0
R5::~R5() this=0x7fffffffe610 x=2, y=3.4

output with R5&& r5 arg:

R5::R5() this=0x7fffffffe5e8 x=2, y=3.4
R5::R5(R5&&) this=0x7fffffffe610 other=0x7fffffffe5e8 x=2, y=3.4
R5::~R5() this=0x7fffffffe5e8 x=0, y=0
R5::~R5() this=0x7fffffffe610 x=2, y=3.4

Copy link
Contributor

@vasild vasild left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 6b24dfe

@sipa
Copy link
Member

sipa commented Aug 31, 2022

Correct. Personally, I think it is fine to have an overhead equivalent to constructing an empty vector. When there is a new call-site with a const reference in the future, the constructor will need to be changed anyway. Either a single one that accepts a pure value, or a new one that accepts a const& (alongside the && constructor), which is what the stdlib is usually doing.

Yes, to be clear: none of the changes in this PR are relevant for performance. CBlockLocators are constructed sufficiently rarely that shaving off (maybe) a microsecond here and there isn't going to be measurable. But if we're already talking about changing the code anyway, it's good to follow best practices.

@maflcko maflcko merged commit b936123 into bitcoin:master Aug 31, 2022
@jonatack jonatack deleted the performance-move-const-arg-fixups branch August 31, 2022 14:02
@maflcko
Copy link
Member

maflcko commented Aug 31, 2022

Going to merge now to unbreak CI. If there is anything else to be done here, it can be done in a follow-up.

sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Sep 1, 2022
…tidy fixup

6b24dfe CBlockLocator: performance-move-const-arg Clang tidy fixups (Jon Atack)

Pull request description:

  Fix Clang-tidy CI errors on master.  See https://cirrus-ci.com/task/4806752200818688?logs=ci#L4696 for an example.

ACKs for top commit:
  MarcoFalke:
    review ACK 6b24dfe
  vasild:
    ACK 6b24dfe

Tree-SHA512: 7a67acf7b42da07b63fbb392236e9a7be8cf35c36e37ca980c4467fe8295c2eda8aef10f41a1e3036cd9ebece47fa957fc3256033f853bd6a97ce2ca42799a0a
@jonatack
Copy link
Member Author

jonatack commented Sep 2, 2022

With a by-value argument there is construct+move+move, with an rval argument, there is construct+move:
rv.cc

output with `R5 r5` arg:

R5::R5() this=0x7fffffffe5e8 x=2, y=3.4
R5::R5(R5&&) this=0x7fffffffe5d8 other=0x7fffffffe5e8 x=2, y=3.4
R5::R5(R5&&) this=0x7fffffffe610 other=0x7fffffffe5d8 x=2, y=3.4
R5::~R5() this=0x7fffffffe5d8 x=0, y=0
R5::~R5() this=0x7fffffffe5e8 x=0, y=0
R5::~R5() this=0x7fffffffe610 x=2, y=3.4


output with `R5&& r5` arg:

R5::R5() this=0x7fffffffe5e8 x=2, y=3.4
R5::R5(R5&&) this=0x7fffffffe610 other=0x7fffffffe5e8 x=2, y=3.4
R5::~R5() this=0x7fffffffe5e8 x=0, y=0
R5::~R5() this=0x7fffffffe610 x=2, y=3.4

@vasild nice! Reproduced this locally for fun.

₿ g++ by-val-versus-rval.cpp && ./a.out 
R5::R5() this=0x7ffe5de81ea0 x=2, y=3.4
R5::R5(R5&&) this=0x7ffe5de81eb0 other=0x7ffe5de81ea0 x=2, y=3.4
R5::R5(R5&&) this=0x7ffe5de81ef0 other=0x7ffe5de81eb0 x=2, y=3.4
R5::~R5() this=0x7ffe5de81eb0 x=0, y=0
R5::~R5() this=0x7ffe5de81ea0 x=0, y=0
R5::~R5() this=0x7ffe5de81ef0 x=2, y=3.4

₿ g++ by-val-versus-rval.cpp && ./a.out 
R5::R5() this=0x7ffea6450b90 x=2, y=3.4
R5::R5(R5&&) this=0x7ffea6450bd0 other=0x7ffea6450b90 x=2, y=3.4
R5::~R5() this=0x7ffea6450b90 x=0, y=0
R5::~R5() this=0x7ffea6450bd0 x=2, y=3.4 

@bitcoin bitcoin locked and limited conversation to collaborators Sep 2, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants