Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why this code is fast for char *?

Tags:

c++

c++11

In this talk by Sutter at 1:15:26 it was presented a code like below,

class employee{
 std::string name_;

public:
template<class String, class=
 std::enable_if_t< !std::is_same<std::decay_t<String>, std::string>::value > >
void set_name(String && name)
   noexcept(std::isnothrow_assignable<std::string &, String>::value)
  {
    name_ = std::forward<String>(name);
  }
}

I know how std::forward works, if name is a lvalue, name_ will get copy constructed; and if name is a rvalue, name_ will get move constructed. But in the slide it also says that Optimized to steal from rvalues (and more), what is more?

And later it shows that this code seems the fastest among all the four implementations, especially for char *, anybody has the patience to walk through this code and explain what more is being optimized and why it is the fastest, particularly in the case of char * ?

like image 864
Allanqunzi Avatar asked Aug 26 '15 01:08

Allanqunzi


People also ask

Which is faster char array or string?

So the character array approach remains significantly faster although less so.

Is char more efficient than string?

char is a primitive data type while String is a class. If you need to store just 1 character, using a char is always better as it would consume lesser memory also using a String to store 1 character is just unrequired as every String also has a lot of methods that are kind of irrelevant for a single character.


1 Answers

First of all, note that the code contains a typo and that the enable_if constraint does not do what's discussed even with the typo removed; In particular the function just won't work with char*, so obviously it's not the fastest with char*. You can see a question I asked about this here along with a 'corrected' perfect forwarding setter (along with the endorsement of this version from Howard Hinnant).

template <class String>
auto set_name(String&& name) 
-> decltype(name_ = std::forward<String>(name), void()) {
    name_ = std::forward<String>(name);
}

The benchmark that's used for the presentation is calling set_name repeatedly on an employee in order to show the case where the member string gets to reuse its capacity instead of there being a memory allocation every iteration. The difference between the tall bars and the short bars shown in the benchmarks is the difference between reusing capacity vs. doing an allocation every iteration.

The reason the corrected perfect forwarder is fast with char* is because the instantiation of the template for char* just forwards a char* instead of needing to construct a string param in order to call set_name with an actual string object parameter. The assignment inside the setter is fast because string implements operator=(char*) that does the efficient memcpy into its existing storage and extra allocations are avoided.

So the claim Optimized to steal from rvalues (and more) is because perfect forwarding does nothing but forwarding. Not only does it pass through an rvalue if it gets an rvalue, but it also doesn't convert types, meaning that optimizations the "forwardee" implements such as string's operator=(char*) get exposed as well.

like image 64
bames53 Avatar answered Oct 04 '22 17:10

bames53