Reputation: 637
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.
gettimeofday()
new
operator gets ()
and initializes the buffer.Upvotes: 1
Views: 423
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