std :: make_unique вызывает значительное замедление?
Недавно я начал модернизировать свою кодовую базу C++, используя C++ 14 вместо C++ 11.
После замены единственного вхождения std::unique_ptr.reset(new...)
с std::make_unique
из C++ 14 я понял, что мой тестовый набор (который состоит из около 30 C++ тестовых программ) составил около 50% помедленнее.
Старый C++ 11 код (быстрый):
class Foo
{
public:
Foo(size_t size)
{
array.reset(new char[size]);
}
private:
std::unique_ptr<char[]> array;
};
Новый код C++ 14 (медленный):
class Foo
{
public:
Foo(size_t size)
{
array = std::make_unique<char[]>(size);
}
private:
std::unique_ptr<char[]> array;
};
Как GCC, так и Clang работают намного медленнее, используя код C++ 14 с std::make_unique
. Когда я тестирую обе версии с использованием valgrind, он сообщает, что оба кода C++ 11 и C++ 14 используют одинаковое количество распределений и одинаковое количество выделенной памяти, и утечек памяти нет.
Когда я смотрю на сгенерированную сборку тестовых программ выше, у меня возникает подозрение, что версия C++ 14 с использованием std::make_unique
сбрасывает память после выделения с использованием memset. Версия C++ 11 не делает этого:
C++ 11 сборка (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 сборка (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
Вопросы:
Является ли инициализация памяти известной функцией std::make_unique
? Если не то, что еще может объяснить замедление производительности, которое я испытываю?
Ответы
Ответ 1
Является ли инициализация памяти известной особенностью std::make_unique
?
Это зависит от того, что вы подразумеваете под "известным". Но да, это разница между вашими делами. Из cppreference вызов make_unique<T>(size)
выполняет:
unique_ptr<T>(new typename std::remove_extent<T>::type[size]())
// ~~~~
Вот как это указано.
new char[size]
выделяет память и инициализирует ее по умолчанию. new char[size]()
выделяет память и инициализирует ее значение, которое инициализируется нулями в случае char
. По умолчанию многие вещи в стандартной библиотеке будут инициализировать значения, а не инициализировать по умолчанию.
Аналогично, make_unique<T>()
делает new T()
, а не new T
... make_unique<char>()
дает вам 0, new char
дает вам неопределенное значение.
В качестве аналогичного примера, если я хочу изменить размер vector<char>
на неинициализированный буфер заданного размера (чтобы он был сразу же заполнен чем-то другим), я должен либо использовать свой собственный распределитель, либо использовать тип, который не char
, которая заключается в его инициализации.
С++ 20 представит новые вспомогательные функции для решения этой проблемы, предоставлено P11020R1:
make_unique_default_init
make_shared_default_init
allocate_shared_default_init
В то время как make_unique<T>()
будет делать new T()
, они делают new T
. То есть дополнительного обнуления нет. В конкретном случае OP, std::make_unique_default_init<char[]>(size)
это то, что вы хотите.