I'm reading Scott's book effective modern c++. In item 26, there's an example that I wrote on Wandbox: https://wandbox.org/permlink/6DKoDqg4jAjA9ZTB
I want to verify how much the good code is better than the bad one. However, the performance comparison is not what I expected, even the good one is slower that the bad one. I don't know what's going wrong.
To prevent wandbox's code from disappearing, here's the code:
#include <iostream>
#include <chrono>
#include <cstdlib>
#include <set>
#include <string>
using namespace std;
std::multiset<std::string> names;
void bad_logAndAdd(const std::string& name) {
auto now = std::chrono::system_clock::now();
names.emplace(name);
}
template <typename T>
void good_logAndAdd(T&& name) {
auto now = std::chrono::system_clock::now();
names.emplace(std::forward<T>(name));
}
void bad() {
for (int i=0; i<1000000; i++) {
string petname("cs");
bad_logAndAdd(petname);
bad_logAndAdd(std::string("abc"));
bad_logAndAdd("dog");
}
}
void good() {
for (int i=0; i<1000000; i++) {
string petname("cs");
good_logAndAdd(petname);
good_logAndAdd(std::string("abc"));
good_logAndAdd("dog");
}
}
int main()
{
auto begin = std::chrono::high_resolution_clock::now();
bad();
auto end = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end-begin).count() << std::endl;
auto begin2 = std::chrono::high_resolution_clock::now();
good();
auto end2 = std::chrono::high_resolution_clock::now();
std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(end2-begin2).count() << std::endl;
}
Perfect forwarding reduces excessive copying and simplifies code by reducing the need to write overloads to handle lvalues and rvalues separately. Note: The function the arguments are forwarded to can be a normal function, another template function, or a constructor.
std::forward helps to implement perfect forwarding. This mechanism implies that objects passed to the function as lvalue expressions should be copied, and objects passed to the function as rvalue expressions should be moved. If you assign an rvalue reference to some ref variable, then ref is a named entity.
I understand that a forwarding reference is "an rvalue reference to a cv-unqualified template parameter", such as in. template <class T> void foo(T&& ); which means the above function can take both l-value and r-value reference.
There are several things that make the difference of behaviour hardly perceptible.
auto now = std::chrono::system_clock::now();
,logAndAdd()
, only two of them can benefit from move (petname
is not implicitly moveable),I have tried to rewrite (below) the OP's code in order to minimise the influence of these things.
now()
at each insertion,In these conditions, I obtain a visible difference between the two solutions (gcc 9.1, linux).
bad: 1.23
good: 1.01
I hope the resulting code is not too far from what was intended in the question.
#include <iostream>
#include <chrono>
#include <cstdlib>
#include <set>
#include <string>
std::multiset<std::string> bad_names;
std::multiset<std::string> good_names;
void bad_logAndAdd(const std::string& name) {
// auto now = std::chrono::system_clock::now();
bad_names.emplace(name);
}
template <typename T>
void good_logAndAdd(T&& name) {
// auto now = std::chrono::system_clock::now();
good_names.emplace(std::forward<T>(name));
}
void bad() {
for (int i=0; i<2000; i++) {
// std::string petname("cs_that_have_been_made_much_longer_in_order_to_prevent_small_string_optimisation_that_makes_copy_and_move_very_similar");
// bad_logAndAdd(petname);
bad_logAndAdd(std::string("abc_that_have_been_made_much_longer_in_order_to_prevent_small_string_optimisation_that_makes_copy_and_move_very_similar"));
bad_logAndAdd("dog_that_have_been_made_much_longer_in_order_to_prevent_small_string_optimisation_that_makes_copy_and_move_very_similar");
}
}
void good() {
for (int i=0; i<2000; i++) {
// std::string petname("cs_that_have_been_made_much_longer_in_order_to_prevent_small_string_optimisation_that_makes_copy_and_move_very_similar");
// good_logAndAdd(petname);
good_logAndAdd(std::string("abc_that_have_been_made_much_longer_in_order_to_prevent_small_string_optimisation_that_makes_copy_and_move_very_similar"));
good_logAndAdd("dog_that_have_been_made_much_longer_in_order_to_prevent_small_string_optimisation_that_makes_copy_and_move_very_similar");
}
}
int main()
{
auto bad_time=std::chrono::high_resolution_clock::duration{};
auto good_time=std::chrono::high_resolution_clock::duration{};
for(auto iter=0; iter<1000; ++iter)
{
bad_names={};
auto begin = std::chrono::high_resolution_clock::now();
bad();
auto end = std::chrono::high_resolution_clock::now();
good_names={};
auto begin2 = std::chrono::high_resolution_clock::now();
good();
auto end2 = std::chrono::high_resolution_clock::now();
if(iter!=0) // ignore warmup
{
bad_time+=end-begin;
good_time+=end2-begin2;
}
}
std::cout << "bad: " << 1e-3*double(std::chrono::duration_cast<std::chrono::milliseconds>(bad_time).count()) << '\n';
std::cout << "good: " << 1e-3*double(std::chrono::duration_cast<std::chrono::milliseconds>(good_time).count()) << '\n';
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With