std::make_unique causes big slowdown?

  • A+

I have recently started to modernize my C++ codebase by using C++14 instead of C++11.

After replacing a single occurrence of std::unique_ptr.reset(new ...) with std::make_unique from C++14 I realized that my test suite (which consists of about 30 C++ test programs) ran about 50% slower.

Old C++11 code (fast):

class Foo {   public:     Foo(size_t size)     {         array.reset(new char[size]);     }   private:     std::unique_ptr<char[]> array; }; 

New C++14 code (slow):

class Foo {   public:     Foo(size_t size)     {         array = std::make_unique<char[]>(size);     }   private:     std::unique_ptr<char[]> array; }; 

Both GCC and Clang run much slower using the C++14 code with std::make_unique. When I test both versions using valgrind it reports that both the C++11 and C++14 code use the same amount of allocations and the same amount of allocated memory and there are no memory leaks.

When I look at the generated assembly of the test programs above I have the suspicion that the C++14 version using std::make_unique resets the memory after the allocation using memset. The C++11 version does not do this:

C++11 assembly (GCC 7.4, x64)

main: sub rsp, 8 movsx rdi, edi call operator new[](unsigned long) mov rdi, rax call operator delete[](void*) xor eax, eax add rsp, 8 ret 

C++14 assembly (GCC 7.4, x64)

main: push rbx movsx rbx, edi mov rdi, rbx call operator new[](unsigned long) mov rcx, rax mov rax, rbx sub rax, 1 js .L2 lea rax, [rbx-2] mov edx, 1 mov rdi, rcx cmp rax, -1 cmovge rdx, rbx xor esi, esi call memset mov rcx, rax .L2: mov rdi, rcx call operator delete[](void*) xor eax, eax pop rbx ret 


Is initializing memory a known feature of std::make_unique? If not what else could explain the performance slowdown I am experiencing?

Is initializing memory a known feature of std::make_unique?

It depends on what you mean by "known." But yeah, that is the difference between your cases. From cppreference, the make_unique<T>(size) call does:

unique_ptr<T>(new typename std::remove_extent<T>::type[size]()) //                                                         ~~~~ 

This is how it is specified.

new char[size] allocates memory and default-initializes it. new char[size]() allocates memory and value-initializes it, which zero-initializes in the case of char. By default, a lot of the things in the standard library will value-initialize and not default initialize.

Likewise, make_unique<T>() does new T() and not new T... make_unique<char>() gives you 0, new char gives you an indeterminate value.

As a similar example, if I want to have a vector<char> resize to an uninitialized buffer of a given size (to be immediately populated by something else), I have to either use my own allocator or use a type that isn't char which lies about its initialization.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: