Young-hwi
Young-hwi

Reputation: 637

The difference between make_unique<T>() and unique_ptr<T>(new T)

I thought that following two are the same: (using C++14)

std::unique_ptr<char[]> buf(std::make_unique<char[]>(size));
std::unique_ptr<char[]> buf(new char[size]);

But when I compiled those two and measured how long each actually took, the result was quite unexpected. Here goes my test code:

#include <stdint.h>
#include <memory>
#include <chrono>

#include <sys/time.h>
#include <sys/types.h>

class Elapsed
{
public:
    Elapsed()
    {
        start();
    }
    int64_t getElapsedMicro()
    {
        std::chrono::steady_clock::time_point end(std::chrono::steady_clock::now());
        return std::chrono::duration_cast<std::chrono::microseconds>(end - mStart).count();
    }
    void start(void)
    {
        mStart = std::chrono::steady_clock::now();
    }

private:
    std::chrono::steady_clock::time_point mStart;
};

static void loop(char *p, size_t aSize)
{
    Elapsed sElapsed;
    for (size_t i = 0; i < aSize; i++)
    {
        if (*(p + i) == 0)
        {
            *(p + i) = i % 128;
        }
        else
        {
            *(p + i) = i % 128;
        }
    }
    printf("%ld usec to loop buffer\n", sElapsed.getElapsedMicro());
}

int32_t main(int32_t aArgc, char *aArgv[])
{
    int32_t sChoice = aArgv[2][0] - '0';
    size_t sSize = strtoll(aArgv[1], NULL, 10);

    Elapsed sDelete;

    switch (sChoice)
    {
        case 1:
            {
                printf("std::unique_ptr<char[]> buf(std::make_unique<char[]>(%zu));\n", sSize);

                Elapsed sElapsed;
                std::unique_ptr<char[]> buf(std::make_unique<char[]>(sSize));
                printf("%ld usec to alloc buffer\n", sElapsed.getElapsedMicro());

                loop(buf.get(), sSize);
                sDelete.start();
            }
            printf("%ld usec to free buffer\n", sDelete.getElapsedMicro());
            break;
        case 2:
            {
                printf("std::unique_ptr<char[]> buf(new char[%zu]);\n", sSize);

                Elapsed sElapsed;
                std::unique_ptr<char[]> buf(new char[sSize]);
                printf("%ld usec to alloc buffer\n", sElapsed.getElapsedMicro());

                loop(buf.get(), sSize);
                sDelete.start();
            }
            printf("%ld usec to free buffer\n", sDelete.getElapsedMicro());
            break;
        case 3:
            {
                printf("std::unique_ptr<char[]> buf((char *)malloc(%zu));\n", sSize);

                Elapsed sElapsed;
                std::unique_ptr<char[]> buf((char *)malloc(sSize));
                printf("%ld usec to alloc buffer\n", sElapsed.getElapsedMicro());

                loop(buf.get(), sSize);
                sDelete.start();
            }
            printf("%ld usec to free buffer\n", sDelete.getElapsedMicro());
            break;
        case 4:
            {
                printf("char *buf = (char *)malloc(%zu);\n", sSize);

                Elapsed sElapsed;
                char *buf = (char *)malloc(sSize);
                printf("%ld usec to alloc buffer\n", sElapsed.getElapsedMicro());

                loop(buf, sSize);
                sDelete.start();
                free(buf);
            }
            printf("%ld usec to free buffer\n", sDelete.getElapsedMicro());
            break;
        case 5:
            {
                printf("std::unique_ptr<char> buf(new char[%zu]);\n", sSize);

                Elapsed sElapsed;
                std::unique_ptr<char> buf(new char[sSize]);
                printf("%ld usec to alloc buffer\n", sElapsed.getElapsedMicro());

                loop(buf.get(), sSize);
                sDelete.start();
            }
            printf("%ld usec to free buffer\n", sDelete.getElapsedMicro());
            break;
        case 6:
            {
                printf("std::unique_ptr<char[]> buf(new char[%zu]());\n", sSize);

                Elapsed sElapsed;
                std::unique_ptr<char[]> buf(new char[sSize]());
                printf("%ld usec to alloc buffer\n", sElapsed.getElapsedMicro());

                loop(buf.get(), sSize);
                sDelete.start();
            }
            printf("%ld usec to free buffer\n", sDelete.getElapsedMicro());
            break;

        default:
            printf("unknown method\n");
            break;

    }

    return 0;
}

and the result:

Take a good look at how many usecs it took. With make_unique it took about 100ms, while new[] took just 42 usecs.

test$ g++ -std=c++14 -Wall -Werror new.cpp -O3

test$ ./a.out 100000000 1
std::unique_ptr<char[]> buf(std::make_unique<char[]>(100000000));
64176 usec to alloc buffer
42887 usec to loop buffer
8683 usec to free buffer

test$ ./a.out 100000000 2
std::unique_ptr<char[]> buf(new char[100000000]);
41 usec to alloc buffer
90317 usec to loop buffer
8322 usec to free buffer

test$ ./a.out 100000000 3
std::unique_ptr<char[]> buf((char *)malloc(100000000));
38 usec to alloc buffer
89392 usec to loop buffer
8311 usec to free buffer

test$ ./a.out 100000000 4
char *buf = (char *)malloc(100000000);
44 usec to alloc buffer
89310 usec to loop buffer
8320 usec to free buffer

test$ ./a.out 100000000 5
std::unique_ptr<char> buf(new char[100000000]);
46 usec to alloc buffer
88960 usec to loop buffer
8315 usec to free buffer

test$ ./a.out 100000000 6
std::unique_ptr<char[]> buf(new char[100000000]());
65898 usec to alloc buffer
42891 usec to loop buffer
8689 usec to free buffer

test$ g++ --version
g++ (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

What makes this difference between following two statements?

std::unique_ptr<char[]> buf(std::make_unique<char[]>(size));
std::unique_ptr<char[]> buf(new char[size]);

valgrind gave me some clue: make_unique() initializes the memory and new does not:

test$ valgrind ./a.out 100 1
==21357== Memcheck, a memory error detector
==21357== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==21357== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==21357== Command: ./a.out 100 1
==21357==
std::unique_ptr<char[]> buf(std::make_unique<char[]>(100));
3880 usec to alloc buffer
555 usec to looping buffer
2927 usec to free buffer
==21357==
==21357== HEAP SUMMARY:
==21357==     in use at exit: 0 bytes in 0 blocks
==21357==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==21357==
==21357== All heap blocks were freed -- no leaks are possible
==21357==
==21357== For counts of detected and suppressed errors, rerun with: -v
==21357== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 6)

test$ valgrind ./a.out 100 2
==21358== Memcheck, a memory error detector
==21358== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==21358== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==21358== Command: ./a.out 100 2
==21358==
std::unique_ptr<char[]> buf(new char[100]);
3224 usec to alloc buffer
==21358== Conditional jump or move depends on uninitialised value(s)
==21358==    at 0x4008E4: loop(char*, unsigned long) (in /home1/irteam/shawn/test/a.out)
==21358==    by 0x400AD9: main (in /home1/irteam/shawn/test/a.out)
==21358==
690 usec to looping buffer
2906 usec to free buffer
==21358==
==21358== HEAP SUMMARY:
==21358==     in use at exit: 0 bytes in 0 blocks
==21358==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==21358==
==21358== All heap blocks were freed -- no leaks are possible
==21358==
==21358== For counts of detected and suppressed errors, rerun with: -v
==21358== Use --track-origins=yes to see where uninitialised values come from
==21358== ERROR SUMMARY: 100 errors from 1 contexts (suppressed: 6 from 6)

test$ valgrind ./a.out 100 6
==7968== Memcheck, a memory error detector
==7968== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==7968== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==7968== Command: ./a.out 100 6
==7968==
std::unique_ptr<char[]> buf(new char[100]());
2509 usec to alloc buffer
2724 usec to looping buffer
788 usec to free buffer
==7968==
==7968== HEAP SUMMARY:
==7968==     in use at exit: 0 bytes in 0 blocks
==7968==   total heap usage: 1 allocs, 1 frees, 100 bytes allocated
==7968==
==7968== All heap blocks were freed -- no leaks are possible
==7968==
==7968== For counts of detected and suppressed errors, rerun with: -v
==7968== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 6)

If the difference is ACTUALLY whether it initializes the memory or not, where in the c++ specifitation can I find it?

BTW, I already have read Differences between std::make_unique and std::unique_ptr.


Edit

Edit2

Upvotes: 1

Views: 423

Answers (1)

Some programmer dude
Some programmer dude

Reputation: 409136

Not from the specification, but this std::make_unique reference says that the array-creation overload is equivalent to

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

The allocation is basically equivalent to

new T[size]()

which according to this new reference (construction section) means each element is value-initialized.

For an array of primitive types, like char, that means each element is initialized to zero.

When you don't value-initialize the array, the array elements will be default constructed, or (for primitive types) will not be initialized at all.

Not initializing a large array is quick. Initializing it, even to all zeroes, is not as quick. Which should explain the difference in execution time.

Upvotes: 1

Related Questions