Fix Clang-tidy CI errors on master. See https://cirrus-ci.com/task/4806752200818688?logs=ci#L4696 for an example.
CBlockLocator: performance-move-const-arg Clang tidy fixup #25963
pull jonatack wants to merge 1 commits into bitcoin:master from jonatack:performance-move-const-arg-fixups changing 2 files +2 −2-
jonatack commented at 11:12 AM on August 31, 2022: contributor
- jonatack marked this as ready for review on Aug 31, 2022
-
jonatack commented at 11:31 AM on August 31, 2022: contributor
The Clang tidy CI passed, so opening this up for review.
-
sipa commented at 11:47 AM on August 31, 2022: member
The change on line 52 makes sense, but the one on 316 seems like a step backwards?
- jonatack force-pushed on Aug 31, 2022
-
jonatack commented at 12:04 PM on August 31, 2022: contributor
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 commented at 12:09 PM on August 31, 2022: member
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 renamed this:
CBlockLocator: performance-move-const-arg Clang tidy fixups
CBlockLocator: performance-move-const-arg Clang tidy fixup
on Aug 31, 2022 -
jonatack commented at 12:27 PM on August 31, 2022: contributor
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 force-pushed on Aug 31, 2022
-
jonatack commented at 12:53 PM on August 31, 2022: contributor
@jonatack Oh! The issue is just that
CBlockLocatorhas no constructor that accepts an rvalueuint256. Thestd::movethere has no effect, but the correct solution is adding an rvalue-accepting constructor, not getting rid of thestd::move.Ah interesting! Learning.
-
vasild commented at 12:54 PM on August 31, 2022: contributor
ACK af7a40c1830c16cb0005a4fb121f631e01be9f80 (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:
<details> <summary>diff</summary>
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);</details>
-
MarcoFalke commented at 12:58 PM on August 31, 2022: member
Using
&&would force all call sites to usestd::move. Thus, it makes it impossible to use onconst&.You can simply change the constructor to allow anything:
explicit CBlockLocator(std::vector<uint256> have) : vHave{std::move(have)} {} -
sipa commented at 1:05 PM on August 31, 2022: member
@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).
-
6b24dfe24d
CBlockLocator: performance-move-const-arg Clang tidy fixups
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 force-pushed on Aug 31, 2022
-
MarcoFalke commented at 1:13 PM on August 31, 2022: member
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 6b24dfe24d2ed50ea0b06ce1919db3dc0e871a03
- MarcoFalke added the label Refactoring on Aug 31, 2022
- MarcoFalke added this to the milestone 24.0 on Aug 31, 2022
-
vasild commented at 1:28 PM on August 31, 2022: contributor
With a by-value argument there is construct+move+move, with an rval argument, there is construct+move:
<details> <summary>rv.cc</summary>
#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; }</details>
output with
R5 r5arg: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.4output with
R5&& r5arg: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 approved
-
vasild commented at 1:29 PM on August 31, 2022: contributor
ACK 6b24dfe24d2ed50ea0b06ce1919db3dc0e871a03
-
sipa commented at 1:46 PM on August 31, 2022: member
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. - MarcoFalke merged this on Aug 31, 2022
- MarcoFalke closed this on Aug 31, 2022
- jonatack deleted the branch on Aug 31, 2022
-
MarcoFalke commented at 2:02 PM on August 31, 2022: member
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 referenced this in commit ddbf7e76be on Sep 1, 2022
-
jonatack commented at 10:05 AM on September 2, 2022: contributor
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](/bitcoin-bitcoin/contributor/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 locked this on Sep 2, 2023